Compare commits

...

9 Commits

Author SHA1 Message Date
1ac53e09a8 Merge pull request #312 from chreddy/patch-6
Updating Danish translation
2021-05-07 15:09:55 +02:00
3412e044df Merge pull request #311 from Drumber/translation
Update German strings.xml
2021-05-07 15:09:30 +02:00
80023da430 Updating Danish translation 2021-05-07 15:09:17 +02:00
e2a2d17506 Update German strings.xml 2021-05-07 13:35:38 +02:00
b93443b736 Added right-aligned widget 2021-05-07 12:21:31 +02:00
9842ba3ea9 Bugfixes 2021-05-06 17:29:29 +02:00
75aba66987 Bugfixes 2021-05-05 18:23:01 +02:00
f325af26f8 Bump version to v2.3.1 2021-05-05 14:54:24 +02:00
b61fbd193c Added single multiple clock 2021-05-05 14:53:43 +02:00
64 changed files with 2492 additions and 1455 deletions

Binary file not shown.

2
.idea/misc.xml generated
View File

@ -61,7 +61,7 @@
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -3,6 +3,7 @@
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@ -22,8 +22,8 @@ android {
applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23
targetSdkVersion 30
versionCode 129
versionName "2.3.0"
versionCode 135
versionName "2.3.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])

View File

@ -41,6 +41,7 @@
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">
<intent-filter>

View File

@ -83,7 +83,7 @@ object Constants {
enum class WidgetAlign(val rawValue: Int) {
LEFT(0),
// RIGHT(1),
RIGHT(1),
CENTER(2)
}
}

View File

@ -84,6 +84,10 @@ object Preferences : KotprefModel() {
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// Clock
var altTimezoneLabel by stringPref(default = "")
var altTimezoneId by stringPref(default = "")
// Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f)

View File

@ -0,0 +1,40 @@
package com.tommasoberlose.anotherwidget.network
import android.content.Context
import android.util.Log
import com.chibatching.kotpref.Kotpref
import com.google.gson.internal.LinkedTreeMap
import com.haroldadmin.cnradapter.NetworkResponse
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.repository.TimeZonesRepository
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class TimeZonesApi(val context: Context) {
suspend fun getTimeZone(lat: String, long: String): String? {
Kotpref.init(context)
val repository = TimeZonesRepository()
var id: String? = null
when (val response = repository.getTimeZone(lat, long)) {
is NetworkResponse.Success -> {
try {
Log.d("ciao", response.body.toString())
id = response.body["timezoneId"] as String
} catch(ex: Exception) {
ex.printStackTrace()
}
}
}
return id
}
}

View File

@ -71,4 +71,13 @@ object ApiServices {
@Query("lon") lon: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface TimeZonesService {
@GET("timezoneJSON")
suspend fun getTimeZone(
@Query("lat") lat: String,
@Query("lng") lon: String,
@Query("username") username: String = "tommaso.berlose",
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
}

View File

@ -0,0 +1,25 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class TimeZonesRepository {
/* YR */
private val apiService: ApiServices.TimeZonesService = getRetrofit().create(ApiServices.TimeZonesService::class.java)
suspend fun getTimeZone(lat: String, long: String) = apiService.getTimeZone(lat, long)
companion object {
private const val BASE_URL_YR = "http://api.geonames.org/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_YR)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,155 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.Manifest
import android.app.Activity
import android.location.Address
import android.location.Geocoder
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.bulk
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityTimeZoneSelectorBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.TimeZonesApi
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.TimeZoneSelectorViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
class TimeZoneSelectorActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: TimeZoneSelectorViewModel
private lateinit var binding: ActivityTimeZoneSelectorBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(TimeZoneSelectorViewModel::class.java)
binding = ActivityTimeZoneSelectorBinding.inflate(layoutInflater)
binding.geonameCredits.movementMethod = LinkMovementMethod.getInstance()
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_location_item) { _, injector ->
injector
.text(R.id.text, getString(R.string.no_time_zone_label))
.clicked(R.id.text) {
Preferences.bulk {
altTimezoneId = ""
altTimezoneLabel = ""
}
MainWidget.updateWidget(this@TimeZoneSelectorActivity)
setResult(Activity.RESULT_OK)
finish()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
injector.text(R.id.text, item.getAddressLine(0))
injector.clicked(R.id.item) {
binding.loader.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val networkApi = TimeZonesApi(this@TimeZoneSelectorActivity)
val id = networkApi.getTimeZone(item.latitude.toString(), item.longitude.toString())
if (id != null) {
Preferences.bulk {
altTimezoneId = id
altTimezoneLabel = try {
item.locality
} catch (ex: Exception) {
item.getAddressLine(0)
}
}
MainWidget.updateWidget(this@TimeZoneSelectorActivity)
setResult(Activity.RESULT_OK)
finish()
} else {
withContext(Dispatchers.Main) {
binding.loader.visibility = View.INVISIBLE
toast(getString(R.string.time_zone_search_error_message))
}
}
}
}
}
.attachTo(binding.listView)
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
})
setupListener()
subscribeUi(binding, viewModel)
binding.location.requestFocus()
setContentView(binding.root)
}
private var searchJob: Job? = null
private fun subscribeUi(binding: ActivityTimeZoneSelectorBinding, viewModel: TimeZoneSelectorViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
binding.loader.visibility = View.INVISIBLE
})
viewModel.locationInput.observe(this, Observer { location ->
binding.loader.visibility = View.VISIBLE
searchJob?.cancel()
searchJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
val list = if (location == null || location == "") {
viewModel.addresses.value!!
} else {
val coder = Geocoder(this@TimeZoneSelectorActivity)
try {
coder.getFromLocationName(location, 10) as ArrayList<Address>
} catch (ignored: Exception) {
emptyList<Address>()
}
}
withContext(Dispatchers.Main) {
viewModel.addresses.value = list
binding.loader.visibility = View.INVISIBLE
}
}
binding.clearSearch.isVisible = location.isNotBlank()
})
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.clearSearch.setOnClickListener {
viewModel.locationInput.value = ""
}
}
}

View File

@ -16,6 +16,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -183,8 +184,6 @@ class MainFragment : Fragment() {
private var uiJob: Job? = null
private fun updateUI() {
uiJob?.cancel()
if (Preferences.showPreview) {
lifecycleScope.launch(Dispatchers.IO) {
val bgColor: Int = ContextCompat.getColor(
@ -207,24 +206,28 @@ class MainFragment : Fragment() {
}
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob?.cancel()
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.getWidgetView(requireContext(), typeface).root
val generatedView = MainWidget.getWidgetView(requireContext(), typeface)?.root
if (generatedView != null) {
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
binding.preview.measure(0, 0)
}
val bitmap = BitmapHelper.getBitmapFromView(
generatedView,
if (binding.preview.width > 0) binding.preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
binding.widgetDetail.content.removeAllViews()
val container = LinearLayout(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
withContext(Dispatchers.Main) {
binding.widgetDetail.bitmapContainer.apply {
setImageBitmap(bitmap)
}
container.gravity = when (Preferences.widgetAlign) {
Constants.WidgetAlign.CENTER.rawValue -> Gravity.CENTER_HORIZONTAL
Constants.WidgetAlign.LEFT.rawValue -> Gravity.START
Constants.WidgetAlign.RIGHT.rawValue -> Gravity.END
else -> Gravity.NO_GRAVITY
}
container.addView(generatedView)
binding.widgetDetail.content.addView(container)
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
@ -234,6 +237,7 @@ class MainFragment : Fragment() {
}
}
}
}
private fun updateClock() {
// Clock
@ -249,6 +253,32 @@ class MainFragment : Fragment() {
)
binding.widgetDetail.timeAmPm.isVisible = Preferences.showAMPMIndicator
// Timezones
if (Preferences.altTimezoneId != "" && Preferences.altTimezoneLabel != "") {
// Clock
binding.widgetDetail.altTimezoneTime.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneTimeAmPm.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneLabel.text = Preferences.altTimezoneLabel
binding.widgetDetail.altTimezoneTime.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTimeAmPm.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneLabel.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTime.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 3
)
binding.widgetDetail.altTimezoneTimeAmPm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.altTimezoneLabel.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.timezonesContainer.isVisible = true
} else {
binding.widgetDetail.timezonesContainer.isVisible = false
}
// Clock bottom margin
binding.widgetDetail.clockBottomMarginNone.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.rawValue
@ -261,7 +291,35 @@ class MainFragment : Fragment() {
// Align
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
gravity = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) Gravity.CENTER_HORIZONTAL else Gravity.NO_GRAVITY
gravity = when (Preferences.widgetAlign) {
Constants.WidgetAlign.CENTER.rawValue -> Gravity.CENTER_HORIZONTAL
Constants.WidgetAlign.LEFT.rawValue -> Gravity.START
Constants.WidgetAlign.RIGHT.rawValue -> Gravity.END
else -> Gravity.NO_GRAVITY
}
}
if (Preferences.widgetAlign == Constants.WidgetAlign.RIGHT.rawValue) {
with (binding.widgetDetail.timeContainer) {
val child = getChildAt(2)
if (child.id == R.id.timezones_container) {
removeViewAt(2)
child.layoutParams = (child.layoutParams as ViewGroup.MarginLayoutParams).apply {
marginEnd = 16f.convertDpToPixel(requireContext()).toInt()
}
addView(child, 0)
}
}
} else {
with (binding.widgetDetail.timeContainer) {
val child = getChildAt(0)
if (child.id == R.id.timezones_container) {
removeViewAt(0)
child.layoutParams = (child.layoutParams as ViewGroup.MarginLayoutParams).apply {
marginEnd = 0
}
addView(child, 2)
}
}
}
}
@ -284,7 +342,7 @@ class MainFragment : Fragment() {
if (showClock) 0f else 1f,
if (showClock) 1f else 0f
).apply {
duration = 300L
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
binding.widgetDetail.timeContainer.layoutParams =
@ -292,6 +350,10 @@ class MainFragment : Fragment() {
height = (initialHeight * animatedValue).toInt()
}
binding.widgetDetail.time.alpha = animatedValue
binding.widgetDetail.timeAmPm.alpha = animatedValue
binding.widgetDetail.altTimezoneTime.alpha = animatedValue
binding.widgetDetail.altTimezoneTimeAmPm.alpha = animatedValue
binding.widgetDetail.altTimezoneLabel.alpha = animatedValue
}
}.start()
}
@ -308,7 +370,7 @@ class MainFragment : Fragment() {
requireContext()
) else 0)
).apply {
duration = 300L
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams

View File

@ -28,6 +28,7 @@ import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.ui.activities.tabs.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.TimeZoneSelectorActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
@ -100,6 +101,17 @@ class ClockFragment : Fragment() {
}
}
viewModel.altTimezoneLabel.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (it != "") {
binding.altTimezoneClockLabel.text =
String.format("%s (%s)", it, Preferences.altTimezoneId)
} else {
binding.altTimezoneClockLabel.text = getString(R.string.no_time_zone_label)
}
}
}
viewModel.showAMPMIndicator.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.ampmIndicatorLabel.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
@ -144,6 +156,10 @@ class ClockFragment : Fragment() {
}.show()
}
binding.actionAltTimezoneClock.setOnClickListener {
startActivity(Intent(requireContext(), TimeZoneSelectorActivity::class.java))
}
binding.actionAmpmIndicatorSize.setOnClickListener {
binding.ampmIndicatorToggle.isChecked = !binding.ampmIndicatorToggle.isChecked
}

View File

@ -101,12 +101,14 @@ class LayoutFragment : Fragment() {
maintainScrollPosition {
binding.widgetAlignIcon.setImageDrawable(when (it) {
Constants.WidgetAlign.LEFT.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_left_24)
Constants.WidgetAlign.RIGHT.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_right_24)
Constants.WidgetAlign.CENTER.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_center_24)
else -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_center_24)
})
binding.widgetAlignLabel.text = when (it) {
Constants.WidgetAlign.LEFT.rawValue -> getString(R.string.settings_widget_align_left_subtitle)
Constants.WidgetAlign.RIGHT.rawValue -> getString(R.string.settings_widget_align_right_subtitle)
Constants.WidgetAlign.CENTER.rawValue -> getString(R.string.settings_widget_align_center_subtitle)
else -> getString(R.string.settings_widget_align_center_subtitle)
}
@ -210,6 +212,10 @@ class LayoutFragment : Fragment() {
getString(R.string.settings_widget_align_left_subtitle),
Constants.WidgetAlign.LEFT.rawValue
)
.addItem(
getString(R.string.settings_widget_align_right_subtitle),
Constants.WidgetAlign.RIGHT.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetAlign = value
}.show()

View File

@ -65,6 +65,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
// Clock Settings
val showClock = Preferences.asLiveData(Preferences::showClock)
val clockTextSize = Preferences.asLiveData(Preferences::clockTextSize)
val altTimezoneLabel = Preferences.asLiveData(Preferences::altTimezoneLabel)
val clockTextColor = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
@ -108,6 +109,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::clockTextAlphaDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::showAMPMIndicator)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockBottomMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::altTimezoneLabel)) { value = true }
}
val widgetPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::textGlobalColor)) { value = true }

View File

@ -0,0 +1,13 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application
import android.location.Address
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.tommasoberlose.anotherwidget.global.Preferences
class TimeZoneSelectorViewModel(application: Application) : AndroidViewModel(application) {
val addresses: MutableLiveData<List<Address>> = MutableLiveData(emptyList())
val locationInput: MutableLiveData<String> = MutableLiveData(Preferences.altTimezoneLabel)
}

View File

@ -0,0 +1,909 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateMargins
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews? {
var views = RemoteViews(context.packageName, if (!rightAligned) R.layout.left_aligned_widget_sans else R.layout.right_aligned_widget_sans)
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
views = ClockWidget(context).updateClockView(views, appWidgetId)
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface) ?: return null
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateGridView(bindingView: LeftAlignedWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false)
)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventsCount > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
}
// Event intent
val eventIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
// Event time difference
if (Preferences.showDiffTime && Calendar.getInstance().timeInMillis < nextEvent.startDate) {
views.setImageViewBitmap(
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
bindingView.nextEventDifferenceTime,
draw = false
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false)
)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.first_line_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
val alarmIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
ContextCompat.getDrawable(
remotePackageContext,
Preferences.lastNotificationIcon)
}
val notificationIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
notificationIntent
)
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && GreetingsHelper.getRandomString(context).isNotBlank()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider&& Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
pIntentDetail
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
} else {
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
}
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): LeftAlignedWidgetBinding? {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
val bindingView = LeftAlignedWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.weatherDateLine.isVisible = true
val currentTemp = String.format(
Locale.getDefault(),
"%d°%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.weatherSubLineWeatherIcon.isVisible = false
bindingView.weatherDateLineWeatherIcon.isVisible = false
} else {
bindingView.weatherSubLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherDateLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherSubLineWeatherIcon.isVisible = true
bindingView.weatherDateLineWeatherIcon.isVisible = true
}
bindingView.weatherDateLineTemperature.text = currentTemp
bindingView.weatherSubLineTemperature.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.weatherSubLine.isVisible = false
}
} else {
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
}
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.nextEventDifferenceTime.isVisible = false
bindingView.actionNext.isVisible = false
bindingView.date.text = DateHelper.getDateText(context, now)
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventsCount > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
bindingView.nextEventDifferenceTime.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.isVisible = true
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place_24
)
)
bindingView.subLineText.text = nextEvent.address
} else {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
if (!nextEvent.allDay) {
val startHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.startDate)
val endHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.endDate)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
val endCal = Calendar.getInstance()
endCal.timeInMillis = nextEvent.endDate
if (startCal.get(Calendar.HOUR_OF_DAY) > endCal.get(Calendar.HOUR_OF_DAY)) {
dayDiff++
} else if (startCal.get(Calendar.HOUR_OF_DAY) == endCal.get(Calendar.HOUR_OF_DAY) && startCal.get(
Calendar.MINUTE
) > endCal.get(Calendar.MINUTE)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
if (nextEvent.startDate != nextEvent.endDate) {
bindingView.subLineText.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
bindingView.subLineText.text =
String.format("%s", startHour)
}
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateUtils.formatDateTime(context, now.timeInMillis, flags)
} else {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = true
bindingView.subLineTopMarginSmall.visibility = View.GONE
bindingView.subLineTopMarginMedium.visibility = View.GONE
bindingView.subLineTopMarginLarge.visibility = View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.subLineIcon.isVisible = true
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note_24
)
)
bindingView.subLineText.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
bindingView.subLineIcon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel != 100) {
bindingView.subLineText.text = context.getString(R.string.charging)
} else {
bindingView.subLineText.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text = Preferences.customNotes
bindingView.subLineText.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext,
Preferences.lastNotificationIcon)
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(icon)
} else {
bindingView.subLineIcon.isVisible = false
}
bindingView.subLineText.text = Preferences.lastNotificationTitle
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
val greetingsText = GreetingsHelper.getRandomString(context)
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && greetingsText.isNotBlank()) {
bindingView.subLineText.text = greetingsText
bindingView.subLineText.maxLines = 2
bindingView.subLineIcon.isVisible = false
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
bindingView.subLineText.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
} else "").trimEnd()
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = false
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else {
bindingView.subLineIcon.isVisible = false
}
}
// Color
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.actionNext)
} else {
listOf<ImageView>(
bindingView.actionNext,
bindingView.weatherDateLineWeatherIcon,
bindingView.weatherSubLineWeatherIcon
)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue()
.toFloat() else Preferences.textGlobalAlpha.toIntValue()
.toFloat()) / 100
}
listOf<TextView>(bindingView.subLineText, bindingView.weatherSubLineDivider, bindingView.weatherSubLineTemperature).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.subLineIcon, bindingView.subLineIconShadow)
} else {
listOf<ImageView>(bindingView.subLineIcon, bindingView.weatherSubLineWeatherIcon, bindingView.subLineIconShadow)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue()
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
// Text Size
listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineTemperature to ((Preferences.textMainSize + Preferences.textSecondSize) / 2),
bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
}
// Icons scale
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherDateLineWeatherIcon.scaleX = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.weatherDateLineWeatherIcon.scaleY = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f
// Shadows
val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> R.color.black_50
2 -> Color.BLACK
else -> R.color.black_50
}
val shadowDy =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 1f
else -> 0f
}
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Icons shadow
listOf(
Pair(bindingView.subLineIcon, bindingView.subLineIconShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first)
}
}
listOf(
Pair(bindingView.actionNext, bindingView.actionNextShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.6f)
}
}
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
val googleSans: Typeface = when (Preferences.customFontVariant) {
"100" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_thin.ttf")
"200" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_light.ttf")
"500" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_medium.ttf")
"700" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_bold.ttf")
"800" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_black.ttf")
else -> Typeface.createFromAsset(context.assets, "fonts/google_sans_regular.ttf")
}
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = typeface
}
}
// Dividers
arrayOf(bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
}
// Right Aligned
if (rightAligned) {
bindingView.mainContent.layoutParams = (bindingView.mainContent.layoutParams as RelativeLayout.LayoutParams).apply {
addRule(RelativeLayout.ALIGN_PARENT_END)
}
bindingView.mainContent.gravity = Gravity.END
bindingView.dateLayout.gravity = Gravity.END
bindingView.calendarLayout.gravity = Gravity.END or Gravity.CENTER_VERTICAL
bindingView.subLineContainer.gravity = Gravity.END or Gravity.CENTER_VERTICAL
}
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null
}
}
}

View File

@ -3,6 +3,7 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.app.PendingIntent
import android.content.Context
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.RemoteViews
import com.tommasoberlose.anotherwidget.R
@ -24,6 +25,7 @@ class ClockWidget(val context: Context) {
views.setViewVisibility(R.id.clock_bottom_margin_small, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_medium, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_large, View.GONE)
views.setViewVisibility(R.id.timezones_container, View.GONE)
} else {
views.setTextColor(R.id.time, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.time_am_pm, ColorHelper.getClockFontColor(context.isDarkTheme()))
@ -64,6 +66,38 @@ class ClockWidget(val context: Context) {
R.id.clock_bottom_margin_large,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
// Timezones
if (Preferences.altTimezoneId != "" && Preferences.altTimezoneLabel != "") {
views.setString(R.id.alt_timezone_time, "setTimeZone", Preferences.altTimezoneId)
views.setString(R.id.alt_timezone_time_am_pm, "setTimeZone", Preferences.altTimezoneId)
views.setTextViewText(R.id.alt_timezone_label, Preferences.altTimezoneLabel)
views.setTextColor(R.id.alt_timezone_time, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.alt_timezone_time_am_pm, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.alt_timezone_label, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextViewTextSize(
R.id.alt_timezone_time,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 3
)
views.setTextViewTextSize(
R.id.alt_timezone_time_am_pm,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
views.setTextViewTextSize(
R.id.alt_timezone_label,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
views.setOnClickPendingIntent(R.id.timezones_container, clockPIntent)
views.setViewVisibility(R.id.timezones_container, View.VISIBLE)
} else {
views.setViewVisibility(R.id.timezones_container, View.GONE)
}
}
} catch (ex: Exception) {
ex.printStackTrace()

View File

@ -1,890 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateMargins
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class LeftAlignedWidget(val context: Context) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews {
var views = RemoteViews(context.packageName, R.layout.left_aligned_widget_sans)
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
views = ClockWidget(context).updateClockView(views, appWidgetId)
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateGridView(bindingView: LeftAlignedWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
val eventRepository = EventRepository(context)
try {
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false)
)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
}
// Event intent
val eventIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
// Event time difference
if (Preferences.showDiffTime && Calendar.getInstance().timeInMillis < nextEvent.startDate) {
views.setImageViewBitmap(
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
bindingView.nextEventDifferenceTime,
draw = false
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false)
)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.first_line_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
val alarmIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
ContextCompat.getDrawable(
remotePackageContext,
Preferences.lastNotificationIcon)
}
val notificationIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
notificationIntent
)
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && GreetingsHelper.getRandomString(context).isNotBlank()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider&& Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
pIntentDetail
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
} else {
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
}
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): LeftAlignedWidgetBinding {
val eventRepository = EventRepository(context)
val bindingView = LeftAlignedWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.weatherDateLine.isVisible = true
val currentTemp = String.format(
Locale.getDefault(),
"%d°%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.weatherSubLineWeatherIcon.isVisible = false
bindingView.weatherDateLineWeatherIcon.isVisible = false
} else {
bindingView.weatherSubLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherDateLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherSubLineWeatherIcon.isVisible = true
bindingView.weatherDateLineWeatherIcon.isVisible = true
}
bindingView.weatherDateLineTemperature.text = currentTemp
bindingView.weatherSubLineTemperature.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.weatherSubLine.isVisible = false
}
} else {
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
}
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.nextEventDifferenceTime.isVisible = false
bindingView.actionNext.isVisible = false
bindingView.date.text = DateHelper.getDateText(context, now)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
bindingView.nextEventDifferenceTime.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.isVisible = true
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place_24
)
)
bindingView.subLineText.text = nextEvent.address
} else {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
if (!nextEvent.allDay) {
val startHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.startDate)
val endHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.endDate)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
val endCal = Calendar.getInstance()
endCal.timeInMillis = nextEvent.endDate
if (startCal.get(Calendar.HOUR_OF_DAY) > endCal.get(Calendar.HOUR_OF_DAY)) {
dayDiff++
} else if (startCal.get(Calendar.HOUR_OF_DAY) == endCal.get(Calendar.HOUR_OF_DAY) && startCal.get(
Calendar.MINUTE
) > endCal.get(Calendar.MINUTE)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
if (nextEvent.startDate != nextEvent.endDate) {
bindingView.subLineText.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
bindingView.subLineText.text =
String.format("%s", startHour)
}
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateUtils.formatDateTime(context, now.timeInMillis, flags)
} else {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = true
bindingView.subLineTopMarginSmall.visibility = View.GONE
bindingView.subLineTopMarginMedium.visibility = View.GONE
bindingView.subLineTopMarginLarge.visibility = View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.subLineIcon.isVisible = true
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note_24
)
)
bindingView.subLineText.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
bindingView.subLineIcon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel != 100) {
bindingView.subLineText.text = context.getString(R.string.charging)
} else {
bindingView.subLineText.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text = Preferences.customNotes
bindingView.subLineText.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext,
Preferences.lastNotificationIcon)
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(icon)
} else {
bindingView.subLineIcon.isVisible = false
}
bindingView.subLineText.text = Preferences.lastNotificationTitle
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
val greetingsText = GreetingsHelper.getRandomString(context)
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && greetingsText.isNotBlank()) {
bindingView.subLineText.text = greetingsText
bindingView.subLineText.maxLines = 2
bindingView.subLineIcon.isVisible = false
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
bindingView.subLineText.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
} else "").trimEnd()
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = false
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else {
bindingView.subLineIcon.isVisible = false
}
}
// Color
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.actionNext)
} else {
listOf<ImageView>(
bindingView.actionNext,
bindingView.weatherDateLineWeatherIcon,
bindingView.weatherSubLineWeatherIcon
)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue()
.toFloat() else Preferences.textGlobalAlpha.toIntValue()
.toFloat()) / 100
}
listOf<TextView>(bindingView.subLineText, bindingView.weatherSubLineDivider, bindingView.weatherSubLineTemperature).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.subLineIcon, bindingView.subLineIconShadow)
} else {
listOf<ImageView>(bindingView.subLineIcon, bindingView.weatherSubLineWeatherIcon, bindingView.subLineIconShadow)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue()
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
// Text Size
listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineTemperature to ((Preferences.textMainSize + Preferences.textSecondSize) / 2),
bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
}
// Icons scale
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherDateLineWeatherIcon.scaleX = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.weatherDateLineWeatherIcon.scaleY = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f
// Shadows
val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> R.color.black_50
2 -> Color.BLACK
else -> R.color.black_50
}
val shadowDy =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 1f
else -> 0f
}
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Icons shadow
listOf(
Pair(bindingView.subLineIcon, bindingView.subLineIconShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first)
}
}
listOf(
Pair(bindingView.actionNext, bindingView.actionNextShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.6f)
}
}
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
val googleSans: Typeface = when (Preferences.customFontVariant) {
"100" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_thin.ttf")
"200" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_light.ttf")
"500" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_medium.ttf")
"700" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_bold.ttf")
"800" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_black.ttf")
else -> Typeface.createFromAsset(context.assets, "fonts/google_sans_regular.ttf")
}
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = typeface
}
}
// Dividers
arrayOf(bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
}
eventRepository.close()
return bindingView
}
}

View File

@ -1,47 +1,19 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.format.DateUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.viewbinding.ViewBinding
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.databinding.TheWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.*
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.min
import kotlin.math.roundToInt
class MainWidget : AppWidgetProvider() {
@ -98,16 +70,18 @@ class MainWidget : AppWidgetProvider() {
WidgetHelper.runWithCustomTypeface(context) {
val views = when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(context, rightAligned = true).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
else -> StandardWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
if (views != null) appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
fun getWidgetView(context: Context, typeface: Typeface?): ViewBinding {
fun getWidgetView(context: Context, typeface: Typeface?): ViewBinding? {
return when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidgetView(typeface)
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidgetView(typeface)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(context, rightAligned = true).generateWidgetView(typeface)
else -> StandardWidget(context).generateWidgetView(typeface)
}
}

View File

@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class StandardWidget(val context: Context) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews? {
var views = RemoteViews(context.packageName, R.layout.the_widget_sans)
@ -73,7 +73,8 @@ class StandardWidget(val context: Context) {
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface)
val generatedBinding = generateWidgetView(typeface) ?: return null
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
@ -88,8 +89,12 @@ class StandardWidget(val context: Context) {
}
private fun updateGridView(bindingView: TheWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
val eventRepository = EventRepository(context)
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
@ -138,7 +143,6 @@ class StandardWidget(val context: Context) {
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
@ -156,7 +160,7 @@ class StandardWidget(val context: Context) {
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
if (Preferences.showNextEvent && eventsCount > 1) {
// Action next event
views.setImageViewBitmap(
@ -402,8 +406,6 @@ class StandardWidget(val context: Context) {
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
@ -411,8 +413,13 @@ class StandardWidget(val context: Context) {
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding {
fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding? {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
val bindingView = TheWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
@ -462,15 +469,14 @@ class StandardWidget(val context: Context) {
bindingView.date.text = DateHelper.getDateText(context, now)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
Preferences.showNextEvent && eventsCount > 1
bindingView.actionPrevious.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
Preferences.showNextEvent && eventsCount > 1
bindingView.nextEvent.text = nextEvent.title
@ -923,8 +929,13 @@ class StandardWidget(val context: Context) {
}
}
eventRepository.close()
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8.5,8.53v3.24c0,0.18 0.09,0.34 0.24,0.43l2.52,1.51c0.23,0.14 0.52,0.06 0.66,-0.16l0,0c0.14,-0.23 0.06,-0.53 -0.16,-0.66L9.5,11.55V8.53c0,-0.26 -0.21,-0.48 -0.48,-0.48H8.98C8.71,8.05 8.5,8.26 8.5,8.53z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M13.9,10c0.07,0.32 0.1,0.66 0.1,1c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5s2.24,-5 5,-5c0.71,0 1.39,0.15 2,0.42V5.35C10.37,5.13 9.7,5 9,5c-3.31,0 -6,2.69 -6,6s2.69,6 6,6s6,-2.69 6,-6c0,-0.34 -0.04,-0.67 -0.09,-1H13.9z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M15,6V4.5C15,4.22 14.78,4 14.5,4h0C14.22,4 14,4.22 14,4.5V6h0h-1.5C12.22,6 12,6.22 12,6.5v0C12,6.78 12.22,7 12.5,7H14v1.5C14,8.78 14.22,9 14.5,9h0C14.78,9 15,8.78 15,8.5V7v0h1.5C16.78,7 17,6.78 17,6.5v0C17,6.22 16.78,6 16.5,6H15z"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10.75,8C10.34,8 10,8.34 10,8.75v4.69c0,0.35 0.18,0.67 0.47,0.85l3.64,2.24c0.33,0.2 0.76,0.11 0.97,-0.21c0.23,-0.34 0.12,-0.8 -0.23,-1.01L11.5,13.3V8.75C11.5,8.34 11.16,8 10.75,8z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.92,12c0.05,0.33 0.08,0.66 0.08,1c0,3.9 -3.1,7 -7,7s-7,-3.1 -7,-7c0,-3.9 3.1,-7 7,-7c0.7,0 1.37,0.1 2,0.29V4.23C12.36,4.08 11.69,4 11,4c-5,0 -9,4 -9,9s4,9 9,9s9,-4 9,-9c0,-0.34 -0.02,-0.67 -0.06,-1H17.92z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M22,5h-2V3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2h-2c-0.55,0 -1,0.45 -1,1c0,0.55 0.45,1 1,1h2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1V7h2c0.55,0 1,-0.45 1,-1C23,5.45 22.55,5 22,5z"/>
</vector>

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.TimeZoneSelectorViewModel" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimaryDark"
tools:context="com.tommasoberlose.anotherwidget.ui.activities.tabs.TimeZoneSelectorActivity">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="2dp"
app:cardCornerRadius="0dp"
android:id="@+id/toolbar"
app:cardBackgroundColor="@color/colorPrimary">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:layout_marginTop="2dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:id="@+id/action_back"
app:tint="@color/colorPrimaryText"
android:src="@drawable/round_arrow_back_24" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:text="@string/time_zones_header"
android:gravity="center"
style="@style/AnotherWidget.Main.Title"/>
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="2dp"
app:cardCornerRadius="0dp"
app:cardBackgroundColor="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/colorPrimaryDark"
app:cardCornerRadius="9dp"
app:cardElevation="0dp">
<EditText
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorPrimaryDark"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:inputType="textCapWords"
android:id="@+id/location"
android:fontFamily="@font/google_sans"
android:gravity="center_vertical|start"
android:hint="@string/search"
android:textAlignment="viewStart"
android:textDirection="locale"
android:text="@={viewModel.locationInput}"
android:autofillHints="" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/round_close"
app:tint="@color/colorPrimaryText"
android:layout_gravity="center_vertical|end"
android:id="@+id/clear_search" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="8dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:padding="4dp"
android:src="@drawable/outline_info_24"
app:tint="@color/colorSecondaryText"/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:linksClickable="true"
android:clickable="true"
android:focusable="true"
android:id="@+id/geoname_credits"
android:textColorLink="@color/colorPrimaryText"
android:text="@string/geoname_credit"
android:textColor="@color/colorSecondaryText"
android:letterSpacing="0"
android:fontFamily="@font/google_sans"
android:textAppearance="@style/AnotherWidget.Settings.Subtitle"
app:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.core.widget.ContentLoadingProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:indeterminateTint="@color/colorAccent"
android:layout_marginTop="-7dp"
android:id="@+id/loader"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:clipToPadding="false"
android:id="@+id/list_view" />
</RelativeLayout>
</LinearLayout>
</layout>

View File

@ -138,6 +138,51 @@
android:text="@string/settings_subtitle_dark_theme_dark"/>
</androidx.cardview.widget.CardView>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/time_zones_header"
android:paddingTop="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textAppearance="@style/AnotherWidget.Settings.Header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:id="@+id/action_alt_timezone_clock"
android:orientation="horizontal">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/round_more_time_24"
app:tint="@color/colorPrimaryText"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/AnotherWidget.Settings.Title"
android:text="@string/settings_alt_timezone_title"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/alt_timezone_clock_label"
style="@style/AnotherWidget.Settings.Subtitle"/>
</LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -16,11 +16,11 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/main_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:visibility="gone"
android:paddingBottom="8dp"
android:orientation="vertical"
@ -63,7 +63,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutDirection="locale"
android:layout_gravity="center_vertical"
android:id="@+id/calendar_layout"
android:visibility="gone"
android:orientation="horizontal">
@ -129,10 +128,11 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false"
android:layout_gravity="center_vertical"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:id="@+id/sub_line_container"
android:layoutDirection="locale"
android:gravity="center_vertical">
<LinearLayout

View File

@ -28,6 +28,7 @@
android:layout_height="wrap_content"
android:format12Hour="h:mm"
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
@ -39,10 +40,58 @@
android:format12Hour="a"
android:format24Hour=""
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timezones_container"
android:visibility="gone"
android:layout_marginStart="16dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="18dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextClock
android:id="@+id/alt_timezone_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="h:mm"
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
<TextClock
android:id="@+id/alt_timezone_time_am_pm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="a"
android:format24Hour=""
android:includeFontPadding="false"
android:padding="0dp"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
</LinearLayout>
<TextView
android:id="@+id/alt_timezone_label"
android:layout_width="wrap_content"
android:gravity="start"
android:maxLines="1"
android:textStyle="bold"
android:includeFontPadding="false"
android:layout_height="wrap_content"
style="@style/AnotherWidget.Widget.Subtitle" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
@ -71,7 +120,8 @@
android:id="@+id/clock_bottom_margin_large" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:id="@+id/content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

View File

@ -0,0 +1,243 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:scaleType="fitXY"
android:id="@+id/widget_shape_background"
android:src="@drawable/card_background" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_alignParentEnd="true"
android:gravity="end"
android:id="@+id/main_layout"
android:animateLayoutChanges="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/time_container"
android:layout_gravity="end"
android:layoutDirection="locale"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timezones_container"
android:visibility="gone"
android:layout_marginStart="0dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="18dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextClock
android:id="@+id/alt_timezone_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="h:mm"
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
<TextClock
android:id="@+id/alt_timezone_time_am_pm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="a"
android:format24Hour=""
android:includeFontPadding="false"
android:padding="0dp"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
</LinearLayout>
<TextView
android:id="@+id/alt_timezone_label"
android:layout_width="wrap_content"
android:gravity="start"
android:maxLines="1"
android:textStyle="bold"
android:includeFontPadding="false"
android:layout_height="wrap_content"
style="@style/AnotherWidget.Widget.Subtitle" />
</LinearLayout>
<TextClock
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="h:mm"
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
<TextClock
android:id="@+id/time_am_pm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="a"
android:format24Hour=""
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="-16dp"
android:orientation="horizontal"
android:id="@+id/clock_bottom_margin_none" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="-8dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/clock_bottom_margin_small" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/clock_bottom_margin_medium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="10dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/clock_bottom_margin_large" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:id="@+id/content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:layout_alignParentEnd="true"
android:id="@+id/bitmap_container"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
android:gravity="end"
android:layout_alignParentEnd="true"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/errorColorText"
android:layoutDirection="locale"
android:visibility="gone"
android:gravity="end"
android:layout_marginBottom="4dp"
android:id="@+id/first_line_rect">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:id="@+id/date_rect"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorNightDark"
android:visibility="gone"
android:id="@+id/weather_rect" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutDirection="locale"
android:visibility="gone"
android:id="@+id/calendar_layout_rect"
android:orientation="horizontal">
<ImageView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:background="@color/colorAccent"
android:id="@+id/next_event_rect" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorNightDark"
android:id="@+id/next_event_difference_time_rect" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:background="@color/colorAccent"
android:layout_gravity="center_vertical"
android:id="@+id/action_next_rect" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/sub_line_top_margin_small_sans" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="8dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/sub_line_top_margin_medium_sans" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="12dp"
android:visibility="gone"
android:orientation="horizontal"
android:id="@+id/sub_line_top_margin_large_sans" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorNightDark"
android:orientation="horizontal"
android:layoutDirection="locale"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:id="@+id/sub_line_rect" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:background="@color/colorNightDark"
android:id="@+id/weather_sub_line_rect" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -30,6 +30,7 @@
android:layout_height="wrap_content"
android:gravity="center"
android:format12Hour="h:mm"
android:includeFontPadding="false"
android:padding="0dp"
android:lines="1"
android:maxLines="1"
@ -41,10 +42,58 @@
android:gravity="center"
android:format12Hour="a"
android:format24Hour=""
android:includeFontPadding="false"
android:padding="0dp"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timezones_container"
android:visibility="gone"
android:layout_marginStart="16dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="20dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextClock
android:id="@+id/alt_timezone_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="h:mm"
android:padding="0dp"
android:lines="1"
android:maxLines="1"
android:includeFontPadding="false"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
<TextClock
android:id="@+id/alt_timezone_time_am_pm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:format12Hour="a"
android:format24Hour=""
android:padding="0dp"
android:includeFontPadding="false"
android:lines="1"
android:maxLines="1"
style="@style/AnotherWidget.Widget.Title"
android:gravity="center_vertical"/>
</LinearLayout>
<TextView
android:id="@+id/alt_timezone_label"
android:layout_width="wrap_content"
android:gravity="start"
android:maxLines="1"
android:textStyle="bold"
android:includeFontPadding="false"
android:layout_height="wrap_content"
style="@style/AnotherWidget.Widget.Subtitle" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
@ -73,7 +122,8 @@
android:id="@+id/clock_bottom_margin_large" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:id="@+id/content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" translatable="false">Another Widget</string>
<string name="add_widget">Tilføj</string>
<string name="add_widget">Tilføj Widget</string>
<!-- Display -->
<string name="settings_general_title">Visning</string>
@ -22,14 +22,19 @@
<string name="alpha">Alpha</string>
<string name="transparent">Gennemsigtig</string>
<string name="settings_show_dividers_title">Vis tekstdelere</string>
<string name="first_row_header">Første række</string>
<string name="second_row_header">Anden række</string>
<string name="first_row_header">Primær tekst</string>
<string name="second_row_header">Sekundær tekst</string>
<string name="global_style_header">Widget</string>
<string name="action_capitalize_the_date">Skift mellem stort og småt begyndelsesbogstav</string>
<string name="settings_date_format_title">Datoformat</string>
<string name="header_widget_background">Widget baggrund</string>
<string name="settings_secondary_row_top_margin_title">Margen</string>
<string name="settings_secondary_row_top_margin_title">Rækkeafstand</string>
<string name="action_custom_font_to_search">Brugerdefineret skrifttype...</string>
<string name="font_family_settings_title">Font familie</string>
<string name="settings_widget_align_title">Justering</string>
<string name="settings_widget_align_left_subtitle">Venstre</string>
<string name="settings_widget_align_right_subtitle">Højre</string>
<string name="settings_widget_align_center_subtitle">Centrer</string>
<string name="font_100" translatable="false">Thin</string>
<string name="font_200" translatable="false">Light</string>
@ -40,6 +45,16 @@
<string name="font_700" translatable="false">Bold</string>
<string name="font_800" translatable="false">Black</string>
<string name="font_900" translatable="false">Heavy</string>
<string name="font_italic" translatable="false">Italic</string>
<string name="font_100_italic" translatable="false">Thin Italic</string>
<string name="font_200_italic" translatable="false">Light Italic</string>
<string name="font_300_italic" translatable="false">Book Italic</string>
<string name="font_400_italic" translatable="false">Regular Italic</string>
<string name="font_500_italic" translatable="false">Medium Italic</string>
<string name="font_600_italic" translatable="false">Semi-bold Italic</string>
<string name="font_700_italic" translatable="false">Bold Italic</string>
<string name="font_800_italic" translatable="false">Black Italic</string>
<string name="font_900_italic" translatable="false">Heavy Italic</string>
<!-- Calendar -->
<string name="settings_calendar_title">Kalender</string>
@ -63,7 +78,7 @@
<string name="settings_show_until_subtitle_5">7 dage senere</string>
<string name="settings_show_until_title">Vis begivenheder mindst</string>
<string name="day_char">d</string>
<string name="settings_calendar_app_title">Standard app til kalender</string>
<string name="settings_calendar_app_title">Kalender</string>
<string name="error_no_calendar">Ingen kalendere fundet.</string>
<string name="tomorrow">i morgen</string>
<string name="today">i dag</string>
@ -93,6 +108,11 @@
<string name="attendee_status_invited">Begivenhedsinvitationer</string>
<string name="attendee_status_declined">Afviste begivenheder</string>
<string name="calendar_sync_notification_channel_id" translatable="false">calendar-sync</string>
<string name="calendar_sync_notification_channel_name">Synkroniseringstjeneste for begivenheder</string>
<string name="calendar_sync_notification_channel_description">Tjeneste som bruges til at synkronisere kalenderens begivenheder.</string>
<string name="calendar_sync_notification_title">Synkroniserer kalender begivenheder…</string>
<!-- Weather -->
<string name="settings_weather_title">Vejr</string>
<string name="title_permission_location">Vis vejret</string>
@ -108,27 +128,19 @@
<string name="settings_weather_refresh_period_subtitle_4">12 timer</string>
<string name="settings_weather_refresh_period_subtitle_5">24 timer</string>
<string name="settings_custom_location_title">Lokation</string>
<string name="custom_location_gps">Brug geolocation</string>
<string name="custom_location_gps">Brug geolokation</string>
<string name="show_weather_visible">Vejrinfo er synlig</string>
<string name="show_weather_not_visible">Vejrinfo er skjult</string>
<string name="settings_weather_app_title">Tryk på vejret åbner</string>
<string name="settings_weather_app_title">Vejr</string>
<string name="settings_weather_provider_api_key_title">Vejr API nøgle</string>
<string name="settings_weather_provider_api_key_subtitle_all_set">Vejrudbyderen er konfigureret korrekt</string>
<string name="settings_weather_provider_api_key_subtitle_not_set">Vejrudbyderen skal konfigureres</string>
<string name="api_key_hint">OpenWeatherMap API nøgle</string>
<string name="default_weather_app">Google Weather</string>
<string name="weather_warning">Vejr fra Google Awareness er forældet. Der kræves nu en OpenWeatherMap API nøgle for at vise vejret i widget\'en.</string>
<string name="api_key_title_1">Opret en OpenWeatherMap konto</string>
<string name="api_key_subtitle_1">Opret en gratis konto hos OpenWeatherMap. Det vil kun tage få minutter.</string>
<string name="api_key_title_2">Kopier din API nøgle</string>
<string name="api_key_subtitle_2">Find menuen API keys i dine kontoindstillinger, og kopier standardnøglen.</string>
<string name="api_key_title_3">Indtast nøglen i app\'en</string>
<string name="api_key_subtitle_3">Indsæt nøglen i feltet ovenfor og gem ændringen. Så snart nøglen er aktiveret, vil vejret blive synligt.</string>
<string name="action_open_provider">Gå til OpenWeatherMap.org</string>
<string name="api_key_info_all_set">Der kan gå op til 10 minutter inden din API nøgle er aktiveret. Vejret vil blive opdateret så snart det er tilgængeligt.</string>
<string name="weather_warning">Google Awareness API er forældet. Der kræves nu en API nøgle fra en tredjepartsudbyder, for at vise vejret i widget\'en.</string>
<string name="settings_weather_icon_pack_title">Ikonpakke</string>
<string name="settings_weather_icon_pack_default">Ikonpakke %d</string>
<string name="background_location_warning">Vi indsamler placeringsdata for at opdatere vejrinformationer, selv når app\'en er lukket eller ikke er i brug.\nVi bruger ikke disse data på andre måder.</string>
<string name="background_location_warning">Når geolokation er aktiveret, indsamler vi placeringsdata for at opdatere vejrinformationer, selv når app\'en er lukket eller ikke er i brug.\nVi bruger ikke disse data på andre måder.</string>
<string name="settings_weather_provider_api">Vejrudbyder</string>
<string name="settings_weather_provider_open_weather" translatable="false">OpenWeatherMap.org</string>
@ -152,15 +164,15 @@
<string name="weather_provider_info_weatherapi_subtitle">Åbn udbyderens websted, opret en personlig konto og indsæt standardnøglen her.</string>
<string name="weather_provider_info_here_subtitle">Åbn udbyderens websted, opret en personlig konto og indsæt standardnøglen her.</string>
<string name="weather_provider_info_accuweather_subtitle">Åbn udbyderens websted, opret en personlig konto og indsæt standardnøglen her.</string>
<string name="weather_provider_info_weather_gov_subtitle" />
<string name="weather_provider_info_yr_subtitle" />
<string name="weather_provider_info_weather_gov_subtitle">\n</string>
<string name="weather_provider_info_yr_subtitle">\n</string>
<string name="weather_provider_error_missing_key">Vejrudbyderen du har valgt kræver en API-nøgle.</string>
<string name="weather_provider_error_wrong_location">Den aktuelle placering understøttes ikke.</string>
<string name="weather_provider_error_missing_location">Vælg en gyldig placering.</string>
<string name="weather_provider_error_expired_key">Det ser ud til, at din API-nøgle er udløbet.</string>
<string name="weather_provider_error_invalid_key">API-nøgle er ikke gyldig eller endnu ikke aktiveret.</string>
<string name="weather_provider_error_misconfigured">Vejrudbyderen er forkert konfigureret.</string>
<string name="weather_provider_error_invalid_key">API-nøgle er ugyldig eller ikke aktiveret endnu.</string>
<string name="weather_provider_error_misconfigured">Vejrudbyderen er konfigureret forkert.</string>
<string name="weather_provider_error_connection">Forbindelsesfejl.</string>
<string name="weather_provider_error_generic">Noget gik galt, tjek vejrudbyderens konfiguration.</string>
<string name="api_key_required_message">Konto påkrævet</string>
@ -169,9 +181,15 @@
<string name="weather_select_a_provider">Vælg en udbyder</string>
<string name="weather_provider_activity_subtitle">Vælg en vejrudbyder fra listen.\nEnkelte udbydere har brug for en gratis personlig konto,\nmen de er normalt mere nøjagtige.</string>
<string name="location_access_notification_channel_id" translatable="false">location-access</string>
<string name="location_access_notification_channel_name">Opdateringstjeneste til vejret</string>
<string name="location_access_notification_channel_description">Tjeneste som bruges til at opdatere vejret, baseret på brugerens nuværende lokation.</string>
<string name="location_access_notification_title">Opdaterer vejret…</string>
<string name="location_access_notification_subtitle">Vi opdaterer vejret baseret på din nuværnde lokation.</string>
<!-- Clock -->
<string name="settings_clock_title">Ur</string>
<string name="settings_clock_app_title">Tryk på ur åbner</string>
<string name="settings_clock_app_title">Ur</string>
<string name="title_show_clock">Vis uret</string>
<string name="show_clock_visible">Uret vises</string>
<string name="show_clock_not_visible">Uret er skjult</string>
@ -180,47 +198,52 @@
<string name="settings_clock_bottom_margin_title">Nedre margen på ur</string>
<string name="settings_clock_bottom_margin_subtitle_none">Ingen</string>
<string name="settings_clock_bottom_margin_subtitle_small">Lille</string>
<string name="settings_clock_bottom_margin_subtitle_medium">Medium</string>
<string name="settings_clock_bottom_margin_subtitle_medium">Mellem</string>
<string name="settings_clock_bottom_margin_subtitle_large">Stor</string>
<string name="clock_warning">Grundet teknologiske begrænsninger har uret ikke den tilpassede skrifttype og tekstskyggerne, som valgt under Typografi sektionen.</string>
<string name="clock_warning">Grundet tekniske begrænsninger har uret ikke de tilpassede skrifttyper og skygger, som valgt under Typografi sektionen.</string>
<string name="settings_clock_text_color_title">Tekstfarve</string>
<string name="settings_ampm_indicator_title">Vis AM/PM indikator</string>
<string name="clock_text_header">Tekststil på ur</string>
<string name="time_zones_header">Tidszoner</string>
<string name="settings_alt_timezone_title">Ekstra ur</string>
<string name="no_time_zone_label">Ingen</string>
<string name="time_zone_search_error_message">Fejl ved søgning efter tidszonen.</string>
<string name="geoname_credit">Tjeneste tilgængelig takket være <a href="http://www.geonames.org/">GeoName</a>.</string>
<!-- Glance -->
<string name="settings_show_next_alarm_title">Vis næste alarm</string>
<string name="settings_at_a_glance_title">Overblik</string>
<string name="settings_show_music_title">Aktuel sang</string>
<string name="settings_request_notification_access">Vi har brug for tilladelse til aflæsning af notifikationer, for at vise den aktuelle sang.</string>
<string name="settings_request_notification_access">Vi har brug for notifikations-tilladelsen, for at vise den aktuelle sang.</string>
<string name="settings_request_last_notification_access">Vi har brug for notifikations-tilladelsen, for at tjekke dine seneste notifikationer.</string>
<string name="settings_request_fitness_access">Vi har brug for et par få tilladelser, til at vise dine daglige skridt fra Google Fit.</string>
<string name="title_show_glance">Vis overbliks-informationer</string>
<string name="description_show_glance_visible">Service aktiveret</string>
<string name="description_show_glance_not_visible">Service deaktiveret</string>
<string name="settings_sort_glance_providers_title">Datakildeprioritet</string>
<string name="settings_sort_glance_providers_subtitle">Skift datakilders prioritet</string>
<string name="settings_sort_glance_providers_subtitle">Skift datakilders prioritet ved at sortere listen nedenfor med drag-and-drop ikonerne</string>
<string name="settings_custom_notes_title">Brugerdefinerede noter</string>
<string name="settings_low_battery_level_title">Batteri</string>
<string name="settings_daily_steps_title">Daglige skridt</string>
<string name="battery_low_warning">Lavt batteriniveau</string>
<string name="daily_steps_counter">%d skridt indtil videre</string>
<string name="settings_low_battery_level_title">Batteri</string>
<string name="battery_low_warning">Lavt batteriniveau</string>
<string name="charging">Oplader</string>
<string name="charged">Opladet</string>
<string name="charged">Fuldt opladet</string>
<string name="providers">Kilder</string>
<string name="glance_info">Glance info will show up only when there are no events displayed and only when a few conditions are verified.</string>
<string name="glance_info">Overbliks-informationer vil kun blive vist, når der ikke er nogle begivenheder vist, og kun når et par betingelser er mødt.</string>
<string name="settings_music_players_filter_title">Musikafspillere</string>
<string name="settings_music_players_filter_subtitle">Vælg dine relevante musikafspillere</string>
<string name="settings_show_music_subtitle">Vi benytter musikafspillerens notifikation til at vise den aktuelle sang.</string>
<string name="settings_low_battery_level_subtitle">Bliv underrettet, når enheden har lavt batteriniveau eller oplader.</string>
<string name="settings_daily_steps_subtitle">Se dine daglige skridt et kort øjeblik efter, når du er færdig med at gå eller løbe.</string>
<string name="settings_show_next_alarm_subtitle">Vis din næste alarm, tjek om din standard ur app er den eneste der angiver alarmer.</string>
<string name="settings_show_next_alarm_subtitle">Vis din næste alarm.\nTjek om din standard ur app er den eneste der angiver alarmer.</string>
<string name="settings_show_next_alarm_app_title">Alarm sat af %s</string>
<string name="settings_show_next_alarm_app_subtitle_wrong">Den næste alarm ser ud til at være forkert.</string>
<string name="settings_show_next_alarm_app_subtitle_correct">Den næste alarm ser ud til at være korrekt.</string>
<string name="settings_show_next_alarm_app_subtitle_wrong">Den næste alarm ser ud til at være forkert</string>
<string name="settings_show_next_alarm_app_subtitle_correct">Den næste alarm ser ud til at være korrekt</string>
<string name="settings_show_notifications_title">Seneste notifikationer</string>
<string name="settings_show_notifications_subtitle">Få et hurtigt overblik over de seneste notifikationer, som blev vist på din enhed.</string>
<string name="settings_show_greetings_title">Hilsener</string>
<string name="settings_show_greetings_subtitle">Se en sej sætning, når du mindst venter det.</string>
<string name="settings_show_greetings_title">Hilsner</string>
<string name="settings_show_greetings_subtitle">Se en sej sætning for at lette din dag, når du mindst venter det.</string>
<string name="google_fit_account_connected">Konto forbundet</string>
<string name="google_fit_account_not_connected">Konto ikke forbundet</string>
<string name="google_fit">Google Fit</string>
@ -228,6 +251,40 @@
<string name="action_disconnect">Afbryd</string>
<string name="applications_filter_title">Applikationer</string>
<string name="applications_filter_subtitle">Vælg hvilke notifikationer der skal vises</string>
<string-array name="morning_greetings" tools:ignore="InconsistentArrays">
<item>Godmorgen!</item>
<item>Hav en god dag!</item>
<item>Klar til at tage afsted?</item>
<item>Vågn op.</item>
<item>Glem ikke din morgenmad!</item>
</string-array>
<string-array name="evening_greetings" tools:ignore="InconsistentArrays">
<item>Godaften!</item>
<item>Hvordan gik din dag?</item>
<item>Har du haft en god dag?</item>
<item>Efter indsats kommer komfort.</item>
<item>Glem ikke at spise aftensmad!</item>
</string-array>
<string-array name="night_greetings" tools:ignore="InconsistentArrays">
<item>Godnat!</item>
<item>Sov godt!</item>
<item>Søde drømme indtil solstrålerne finder dig!</item>
<item>Vi ses i morgen...</item>
<item>Bliv ikke oppe for længe!</item>
</string-array>
<string name="glance_notification_hide_timeout_title">Skjul notifikationen efter</string>
<string-array name="glance_notifications_timeout">
<item>30 sekunder</item>
<item>1 minut</item>
<item>5 minutter</item>
<item>10 minutter</item>
<item>15 minutter</item>
<item>Afvisning</item>
</string-array>
<string name="settings_show_events_as_glance_provider_title">Begivenheder</string>
<string name="settings_show_events_as_glance_provider_subtitle">Se et smugkig på dine kalenderbegivenheder, og vis altid den aktuelle dato.</string>
<string name="settings_show_events_as_glance_provider_error">Aktiver visningen af begivenhederne i kalenderfanen, og giv den nødvendige tilladelse.</string>
<string name="events_glance_provider_format">%1$s %2$s</string>
<!-- Settings -->
<string name="action_share">Del</string>
@ -238,7 +295,7 @@
<string name="action_refresh_widget">Genindlæs widget</string>
<string name="toolbar_transition_name" translatable="false">toolbar</string>
<string name="error_opening_uri">Fejl ved åbning af URL: Link kopieret til udklipsholder.</string>
<string name="loading_text">Indlæser data</string>
<string name="loading_text">Indlæser data...</string>
<string name="error_opening_app">Fejl ved åbning af app.</string>
<string name="default_name">Standard app</string>
<string name="action_save">Gem</string>
@ -260,7 +317,7 @@
<string name="settings_app_version_title">App version</string>
<string name="settings_title_show_wallpaper">Vis baggrundsbillede</string>
<string name="support_refresh_widget_subtitle">Tving en genstart af widget-tjenesten</string>
<string name="settings_feedback_subtitle">Dette er et open-source projekt, du er velkommen til at hjælpe.</string>
<string name="settings_feedback_subtitle">Du er velkommen til at hjælpe med projektet.</string>
<string name="settings_feedback_title">Feedback og feature requests</string>
<string name="xiaomi_manufacturer" translatable="false">xiaomi</string>
<string name="xiaomi_warning_title">Xiaomi Enheder</string>
@ -275,7 +332,7 @@
<string name="preferences_header">Præferencer</string>
<string name="settings_privacy_policy_title"><![CDATA[Juridisk og privatliv]]></string>
<string name="settings_privacy_policy_subtitle">Se appens privatlivspolitik</string>
<string name="project_header">Projekt</string>
<string name="project_header">Open-source projekt</string>
<string name="information_header">Information</string>
<!-- Activities -->
@ -294,4 +351,21 @@
<string name="nothing">Intet</string>
<string name="action_dismiss">Afvis</string>
<string name="action_accept">Accepter</string>
<string name="gestures_do_nothing">Ingen</string>
<string name="song_info_format_activity_title">Format på sang</string>
<string name="song_info_format_activity_subtitle">Skift den synlige sanginformation</string>
<!-- More -->
<string name="look_and_feel_header"><![CDATA[Look & feel]]></string>
<string name="typography_settings_title">Typografi</string>
<string name="typography_settings_subtitle">Angiv skrifttype, størrelse og farve</string>
<string name="layout_settings_title">Layout</string>
<string name="gestures_settings_subtitle">Handlinger, adfærd og genveje</string>
<string name="gestures_settings_title">Bevægelser</string>
<string name="gestures_amp_input_header"><![CDATA[Gestures & input]]></string>
<string name="clock_settings_subtitle">Angiv størrelse, farve og tidszoner</string>
<string name="layout_settings_subtitle">Widgetafstand og justeringer</string>
<string name="smart_content_header">Smart indhold</string>
<string name="spacing_settings_header">Mellemrum</string>
<string name="gestures_refresh_widget">Ingen, widget\'en vil blive opdateret</string>
</resources>

View File

@ -28,6 +28,11 @@
<string name="header_widget_background">Widget Hintergrund</string>
<string name="settings_secondary_row_top_margin_title">Zeilenabstand</string>
<string name="action_custom_font_to_search">Benutzerdefinierte Schriftart…</string>
<string name="font_family_settings_title">Schriftfamilie</string>
<string name="settings_widget_align_title">Widget Ausrichtung</string>
<string name="settings_widget_align_left_subtitle">Links</string>
<string name="settings_widget_align_right_subtitle">Rechts</string>
<string name="settings_widget_align_center_subtitle">Zentriert</string>
<!-- Calendar -->
<string name="settings_calendar_title">Kalender</string>
@ -81,6 +86,10 @@
<string name="attendee_status_invited">Einladungen zu Terminen</string>
<string name="attendee_status_declined">Abgelehnte Termine</string>
<string name="calendar_sync_notification_channel_name">Termine Synchronisierungsdienst</string>
<string name="calendar_sync_notification_channel_description">Dienst, der zum Synchronisieren der Kalendertermine verwendet wird.</string>
<string name="calendar_sync_notification_title">Synchronisiere Kalendertermine…</string>
<!-- Weather -->
<string name="settings_weather_title">Wetter</string>
<string name="title_permission_location">Wetter anzeigen</string>
@ -139,7 +148,7 @@
<string name="weather_select_a_provider">Wählen einen Anbieter</string>
<string name="weather_provider_activity_subtitle">Wählen Sie einen Wetterdienst aus der Liste aus.\nEinige Anbieter benötigen ein kostenloses\nBenutzerkonto, allerdings sind sie\ndafür in der Regel genauer.</string>
<string name="location_access_notification_channel_name">Hintergrunddienst</string>
<string name="location_access_notification_channel_name">Wetter Aktualisierungsdienst</string>
<string name="location_access_notification_channel_description">Dienst zur Aktualisierung des Wetters anhand des aktuellen Standorts des Benutzers.</string>
<string name="location_access_notification_title">Wetter wird aktualisiert…</string>
<string name="location_access_notification_subtitle">Das Wetter wird basierend auf Ihrem aktuellen Standort aktualisiert.</string>
@ -161,6 +170,11 @@
<string name="settings_clock_text_color_title">Schriftfarbe</string>
<string name="settings_ampm_indicator_title">Zeige AM/PM</string>
<string name="clock_text_header">Uhr Textstil</string>
<string name="time_zones_header">Zeitzonen</string>
<string name="settings_alt_timezone_title">Mehrere Uhren</string>
<string name="no_time_zone_label">Keine</string>
<string name="time_zone_search_error_message">Fehler beim Suchen der Zeitzone.</string>
<string name="geoname_credit">Service verfügbar dank <a href="http://www.geonames.org/">GeoName</a>.</string>
<!-- Glance -->
<string name="settings_show_next_alarm_title">Nächster Wecker</string>
@ -301,6 +315,9 @@
<string name="nothing">Nichts</string>
<string name="action_dismiss">Verwerfen</string>
<string name="action_accept">Akzeptieren</string>
<string name="gestures_do_nothing">Nichts</string>
<string name="song_info_format_activity_title">Songinfo-Format</string>
<string name="song_info_format_activity_subtitle">Ändere das Songinfo-Format</string>
<!-- More -->
<string name="look_and_feel_header"><![CDATA[Erscheinungsbild]]></string>
@ -313,5 +330,6 @@
<string name="clock_settings_subtitle">Größe, Farbe und Zeitzonen einstellen</string>
<string name="layout_settings_subtitle">Widget Abstände und Anpassungen</string>
<string name="smart_content_header">Smarte Inhalte</string>
<string name="font_family_settings_title">Schriftfamilie</string>
<string name="spacing_settings_header">Abstände</string>
<string name="gestures_refresh_widget">Keine, das Widget wird aktualisiert.</string>
</resources>

View File

@ -204,6 +204,11 @@
<string name="settings_clock_text_color_title">Text color</string>
<string name="settings_ampm_indicator_title">Show AM/PM indicator</string>
<string name="clock_text_header">Clock text style</string>
<string name="time_zones_header">Time Zones</string>
<string name="settings_alt_timezone_title">Multiple clock</string>
<string name="no_time_zone_label">None</string>
<string name="time_zone_search_error_message">Error searching the time zone.</string>
<string name="geoname_credit">Service available thanks to <a href="http://www.geonames.org/">GeoName</a>.</string>
<!-- Glance -->
<string name="settings_show_next_alarm_title">Next clock alarm</string>

View File

@ -10,7 +10,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.5'

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip