Compare commits

..

63 Commits

Author SHA1 Message Date
6d7d90e762 Correct the algorithm to check if to show weather as a glance provider. 2021-09-14 13:49:52 +08:00
e89b377b68 Fix crash in MainFragment.updateUI() on some devices. 2021-09-14 13:48:23 +08:00
e0eb6f77da Merge branch 'patch-4' into patch-develop 2021-09-14 13:40:14 +08:00
43c08204c3 Merge branch 'patch-3' into patch-develop 2021-09-14 13:39:59 +08:00
6f125573e0 Merge branch 'patch-2' into patch-develop
# Conflicts:
#	app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/AlignedWidget.kt
#	app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/StandardWidget.kt
2021-09-14 13:39:30 +08:00
380dc96c40 Merge branch 'patch-1' into patch-develop 2021-09-14 13:38:49 +08:00
821980e938 Use getBroadcast instead of getActivity for ACTION_REFRESH intents. 2021-09-13 23:34:54 +08:00
9f47d626a9 setExact is more reliable than setRepeating on some ROMs (e.g., MIUI). 2021-09-13 23:24:58 +08:00
d3b623cf13 Add ACCESS_BACKGROUND_LOCATION permission required by Android 11. 2021-09-13 19:28:47 +08:00
1389ddbfc0 Avoid the initial vertical position of BottomSheetDialogs being capped. 2021-09-13 11:55:29 +08:00
0dbbe0e5d2 Fix RenderScript leaks; set a reasonable default value for PREF_TEXT_CLOCK_SIZE. 2021-09-10 23:15:47 +08:00
818b4ec0ba Avoid redundant updates. 2021-09-04 18:35:39 +08:00
8c84913cd8 Remove trailing semicolons in code. 2021-09-03 20:54:02 +08:00
43f085b13c Remove extra spacing after event time when weather disabled. 2021-09-03 20:40:44 +08:00
3ab42fd163 Correct and improve calendar events updating algorithm.
1. UpdateCalendarService will fetch all events from now to the next fetch time + Preferences.showUntil, so that EventRepository holds all events need to be shown before the next fetch;
2. Every event is updated 1 minute after the corresponding time span, to make the actual time difference consistent with the displayed getRelativeTimeSpanString;
3. Update events only when necessary, eliminate redundant alarms; cancel all update alarms when showEvents is turned off.
2021-09-03 18:20:01 +08:00
ea0be72478 Do not schedule unnecessary ACTION_ALARM_UPDATE alarm. 2021-09-02 13:54:24 +08:00
c744d3c7f8 Improve translation. 2021-08-19 11:31:24 +08:00
7b93548b0b Use any available Maps app, not just explicitly Google Maps. 2021-08-16 12:39:11 +08:00
260e36b305 Show & hide all-day events at the right time. 2021-08-15 19:13:46 +08:00
00af2159ea Correct some translations. 2021-08-15 18:06:11 +08:00
c26021de03 Fix #321; Update Simplified Chinese. 2021-08-14 00:55:37 +08:00
218dae8cc2 When tapping the event time, it is more reasonable to open the calendar rather than the event details (tap the event title for the details). 2021-08-13 22:10:44 +08:00
b3e2d8d843 Fix FontRequest thread leaks and UI bugs related to custom fonts. 2021-08-13 22:09:46 +08:00
80d1077dab Make LocationService work in case GMS is not available. 2021-08-12 14:12:30 +08:00
dd2a74aaf6 Close #310, added weather as glance provider 2021-05-14 17:15:34 +02:00
fb1953d513 Added custom widget margin and padding, added new preview manager 2021-05-14 16:10:56 +02:00
b081b9adbb Merge pull request #327 from Moutony/patch-31
Update French to try fixing #321
2021-05-14 16:10:16 +02:00
fd398faf42 Update French to try fixing #321 2021-05-14 11:41:43 +02:00
e14662c534 Merge pull request #324 from Moutony/patch-30
French update v2.3.3 (139) - 13th May 2021
2021-05-14 08:57:58 +02:00
4224562512 Merge pull request #323 from Moutony/patch-29
GeoNames was missing an S
2021-05-14 08:57:51 +02:00
f13d426831 Fixed : Widget alignment 2021-05-14 02:33:56 +02:00
5dc7a1b30d Fixed : Widget alignment 2021-05-14 02:25:29 +02:00
6538cdebc2 Text alignment 2021-05-13 09:20:04 +02:00
78709ed018 Text alignment is more relevent than Widget align
Alignment is more correct too
2021-05-13 09:18:58 +02:00
c0e87047c2 French update v2.3.3 (139) - 13th May 2021
Loving the new logo!
2021-05-13 09:06:53 +02:00
1a2c97d61f GeoNames was missing an S 2021-05-13 08:58:28 +02:00
4335751749 Merge pull request #320 from Drumber/translation
Updated German translation
2021-05-12 20:44:24 +02:00
9bbc816bea Merge pull request #319 from chreddy/patch-8
Updating Danish translation
2021-05-12 20:44:02 +02:00
e8a6743dc4 Updated German translation 2021-05-12 18:28:42 +02:00
5d8ceb98cc Updating Danish translation
Adding a translation for default_apps_settings_header, making the translation complete
2021-05-11 23:02:22 +02:00
dfff4c5e1b Merge branch 'develop' into main 2021-05-11 21:05:26 +02:00
13f8814480 Bumped the version 2021-05-11 21:05:19 +02:00
8608f9adf3 Merge branch 'main' of github.com:tommasoberlose/another-widget into develop 2021-05-11 21:01:56 +02:00
ad65cf0e84 Merge branch 'main' of github.com:tommasoberlose/another-widget into main 2021-05-11 21:01:31 +02:00
1ecaf7a11a Bumped the version 2021-05-11 21:00:57 +02:00
2578566659 Fixed #317 2021-05-11 20:56:21 +02:00
61fc0da8d0 New icon 2021-05-11 18:46:08 +02:00
285b754dd5 Merge pull request #315 from Drumber/translation
Update German translation
2021-05-10 12:36:56 +02:00
194a2ab456 Merge pull request #316 from Moutony/patch-28
Update French 9th May 2021
2021-05-10 12:36:44 +02:00
bca22d5aa8 Merge pull request #314 from chreddy/patch-7
Final update - Danish translation
2021-05-10 12:36:36 +02:00
35536a89a0 Update French 9th May 2021 2021-05-09 21:23:55 +02:00
6780a470e9 Update German translation
Added new strings
2021-05-08 09:23:38 +02:00
6ca6b5ab95 Final update - Danish translation
I realized I forgot to update a few strings. Should be all done now.

I also just noticed that there's no string for the text "Default apps" in the Gestures menu, right above the app selection for calendar, weather and clock. So this text keeps showing up in English on my phone, even though the translation is complete.
2021-05-07 20:30:29 +02:00
7edb0635a7 Merge pull request #313 from chreddy/patch-5
Update Danish translation
2021-05-07 17:35:29 +02:00
d72ddd6d85 Update Danish translation 2021-05-07 17:33:24 +02:00
5d07cc8d73 Added color copy/paste, better size and color text selection 2021-05-07 17:19:23 +02:00
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
271 changed files with 3678 additions and 2190 deletions

View File

@ -19,6 +19,29 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
@ -45,14 +68,47 @@
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="$USER_HOME$/Desktop/logo-white.png" />
<entry key="scalingPercent" value="70" />
<entry key="trimmed" value="true" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
@ -64,7 +120,6 @@
<map>
<entry key="backgroundAssetType" value="COLOR" />
<entry key="backgroundColor" value="ffffff" />
<entry key="foregroundImage" value="$USER_HOME$/Desktop/Artboard Copy 3.png" />
<entry key="legacyIconShape" value="CIRCLE" />
</map>
</option>
@ -77,6 +132,29 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
@ -98,6 +176,104 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvBanner">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvChannel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">

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 130
versionName "2.3.1"
versionCode 139
versionName "2.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])

View File

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -13,6 +14,7 @@
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<application
android:allowBackup="true"
@ -24,24 +26,24 @@
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.MainActivity" android:launchMode="singleInstance" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<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" />
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.SupportDevActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">
<intent-filter>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -31,22 +31,5 @@ class AWApplication : Application() {
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)
calibrateVersions()
}
private fun calibrateVersions() {
// 2.0 Tolerance
if (Preferences.clockTextSize > 50f) {
Preferences.clockTextSize = 32f
}
if (Preferences.textMainSize > 36f) {
Preferences.textMainSize = 32f
}
if (Preferences.textSecondSize > 28f) {
Preferences.textSecondSize = 24f
}
}
}

View File

@ -3,40 +3,30 @@ package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.SeekBar
import androidx.annotation.ColorInt
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuHorBinding
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuListBinding
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.copyToClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isClipboardColor
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.pasteFromClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.reveal
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.util.prefs.Preferences
class BottomSheetColorPicker(
context: Context,
@ -46,20 +36,45 @@ class BottomSheetColorPicker(
private val onColorSelected: ((selectedValue: Int) -> Unit)? = null,
private val showAlphaSelector: Boolean = false,
private val alpha: Int = 0,
private val onAlphaChangeListener: ((alpha: Int) -> Unit)? = null
private val onAlphaChangeListener: ((alpha: Int) -> Unit)? = null,
private val hideCopyPaste: Boolean = false,
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter
private var alphaDebouncing: Job? = null
private var binding: BottomSheetMenuHorBinding = BottomSheetMenuHorBinding.inflate(LayoutInflater.from(context))
private var listBinding: BottomSheetMenuListBinding = BottomSheetMenuListBinding.inflate(LayoutInflater.from(context))
override fun show() {
window?.setDimAmount(0f)
// Header
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
if (hideCopyPaste) {
binding.actionContainer.isVisible = false
} else {
binding.actionContainer.isVisible = true
binding.actionCopy.setOnClickListener {
context.copyToClipboard(getSelected?.invoke(), alpha)
}
binding.actionPaste.setOnClickListener {
context.pasteFromClipboard { color, alpha ->
binding.alphaSelector.setProgress(alpha.toIntValue().toFloat())
adapter.notifyItemChanged(adapter.data.indexOf(getSelected?.invoke()))
onColorSelected?.invoke(Color.parseColor(color))
val idx = colors.toList().indexOf(getSelected?.invoke())
adapter.notifyItemChanged(idx)
(listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(idx,0)
}
}
binding.actionPaste.isVisible = context.isClipboardColor()
}
// Alpha
binding.alphaSelectorContainer.isVisible = showAlphaSelector
binding.alphaSelector.setProgress(alpha.toFloat())
@ -67,8 +82,15 @@ class BottomSheetColorPicker(
binding.alphaSelector.onSeekChangeListener = object : OnSeekChangeListener {
override fun onSeeking(seekParams: SeekParams?) {
seekParams?.let {
binding.textAlpha.text = "%s: %s%%".format(context.getString(R.string.alpha), it.progress)
onAlphaChangeListener?.invoke(it.progress)
binding.textAlpha.text =
"%s: %s%%".format(context.getString(R.string.alpha), it.progress)
alphaDebouncing?.cancel()
alphaDebouncing = GlobalScope.launch(Dispatchers.IO) {
delay(150)
withContext(Dispatchers.Main) {
onAlphaChangeListener?.invoke(it.progress)
}
}
}
}
override fun onStartTrackingTouch(seekBar: IndicatorSeekBar?) {
@ -78,7 +100,6 @@ class BottomSheetColorPicker(
}
// List
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
@ -120,14 +141,20 @@ class BottomSheetColorPicker(
adapter.updateData(colors.toList())
withContext(Dispatchers.Main) {
binding.colorLoader.isVisible = false
binding.loader.isVisible = false
binding.listContainer.addView(listBinding.root)
this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
binding.listContainer.isVisible = true
val idx = colors.toList().indexOf(getSelected?.invoke())
(listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(idx,0)
}
})
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -6,6 +6,7 @@ import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.tommasoberlose.anotherwidget.R
@ -107,6 +108,10 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
}
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -0,0 +1,111 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuHorBinding
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuListBinding
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.copyToClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isClipboardColor
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.pasteFromClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
class BottomSheetPicker<T>(
context: Context,
private val items: List<MenuItem<T>> = arrayListOf(),
private val getSelected: (() -> T)? = null,
private val header: String? = null,
private val onItemSelected: ((selectedValue: T?) -> Unit)? = null,
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter
private var binding: BottomSheetMenuHorBinding = BottomSheetMenuHorBinding.inflate(
LayoutInflater.from(context))
private var listBinding: BottomSheetMenuListBinding = BottomSheetMenuListBinding.inflate(
LayoutInflater.from(context))
override fun show() {
window?.setDimAmount(0f)
// Header
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
// Alpha
binding.alphaSelectorContainer.isVisible = false
binding.actionContainer.isVisible = false
// List
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
listBinding.root.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(context)
listBinding.root.layoutManager = mLayoutManager
adapter
.register<Int>(R.layout.bottom_sheet_menu_item) { position, injector ->
val item = items[position]
val isSelected = item.value == getSelected?.invoke()
injector
.text(R.id.label, item.title)
.textColor(R.id.label, ContextCompat.getColor(context, if (isSelected) R.color.colorAccent else R.color.colorSecondaryText))
.selected(R.id.item, isSelected)
.clicked(R.id.item) {
val oldIdx = items.toList().indexOfFirst { it.value == getSelected?.invoke() }
onItemSelected?.invoke(item.value)
adapter.notifyItemChanged(position)
adapter.notifyItemChanged(oldIdx)
(listBinding.root.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position,0)
}
}
.attachTo(listBinding.root)
adapter.updateData((items.indices).toList())
withContext(Dispatchers.Main) {
binding.loader.isVisible = false
binding.listContainer.addView(listBinding.root)
binding.listContainer.isVisible = true
val idx = items.toList().indexOfFirst { it.value == getSelected?.invoke() }
(listBinding.root.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(idx,0)
}
})
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
override fun onStop() {
loadingJobs.forEach { it.cancel() }
super.onStop()
}
class MenuItem<T>(val title: String, val value: T? = null)
}

View File

@ -61,5 +61,9 @@ class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit)
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -26,5 +26,9 @@ class CustomNotesDialog(context: Context, callback: (() -> Unit)?) : BottomSheet
binding.notes.requestFocus()
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -51,6 +51,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_title)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_title)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_title)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_title)
}
/* SUBTITLE*/
@ -63,6 +64,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_subtitle)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_subtitle)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_subtitle)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_subtitle)
}
/* SONG */
@ -140,6 +142,13 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
checkCalendarConfig()
}
/* WEATHER */
if (provider == Constants.GlanceProviderId.WEATHER) {
binding.header.isVisible = false
binding.divider.isVisible = false
checkWeatherConfig()
}
/* TOGGLE */
binding.providerSwitch.setCheckedImmediatelyNoEvent(when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic
@ -150,6 +159,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications
Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings
Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider
Constants.GlanceProviderId.WEATHER -> Preferences.showWeatherAsGlanceProvider
})
var job: Job? = null
@ -208,6 +218,9 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked
}
Constants.GlanceProviderId.WEATHER -> {
Preferences.showWeatherAsGlanceProvider = isChecked
}
else -> {
}
}
@ -217,10 +230,16 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
private fun checkNextAlarm() {
if (!Preferences.showNextAlarm)
AlarmHelper.clearTimeout(context)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) {
@ -255,6 +274,19 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
}
private fun checkWeatherConfig() {
if (!Preferences.showWeather || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || Preferences.weatherProviderLocationError != "") {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_show_weather_as_glance_provider_error)
binding.warningContainer.setOnClickListener {
dismiss()
EventBus.getDefault().post(MainFragment.ChangeTabEvent(1))
}
} else {
binding.warningContainer.isVisible = false
}
}
private fun checkNotificationPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {

View File

@ -50,6 +50,10 @@ class IconPackSelector(context: Context, private val header: String? = null) : B
binding.menu.addView(itemBinding.root)
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
}

View File

@ -57,6 +57,10 @@ class MaterialBottomSheetDialog(
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -64,7 +64,7 @@ class EventRepository(val context: Context) {
else -> add(Calendar.HOUR, 6)
}
}
val event = if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate < limit.timeInMillis) {
val event = if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate <= limit.timeInMillis) {
nextEvent
} else {
val events = getEvents()
@ -105,7 +105,6 @@ class EventRepository(val context: Context) {
} else {
resetNextEventData()
}
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
}
@ -121,7 +120,6 @@ class EventRepository(val context: Context) {
} else {
resetNextEventData()
}
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
}

View File

@ -23,6 +23,13 @@ object Constants {
LARGE(3)
}
enum class Dimension(val rawValue: Float) {
NONE(0f),
SMALL(8f),
MEDIUM(16f),
LARGE(24f)
}
enum class GlanceProviderId(val id: String) {
PLAYING_SONG("PLAYING_SONG"),
NEXT_CLOCK_ALARM("NEXT_CLOCK_ALARM"),
@ -31,7 +38,8 @@ object Constants {
GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS"),
NOTIFICATIONS("NOTIFICATIONS"),
GREETINGS("GREETINGS"),
EVENTS("EVENTS");
EVENTS("EVENTS"),
WEATHER("WEATHER");
companion object {
private val map = GlanceProviderId.values().associateBy(GlanceProviderId::id)
@ -83,7 +91,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)
// UI
var widgetMargin by floatPref(default = Constants.Dimension.SMALL.rawValue)
var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue)
// Clock
var altTimezoneLabel by stringPref(default = "")
var altTimezoneId by stringPref(default = "")
@ -91,7 +95,7 @@ object Preferences : KotprefModel() {
// Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 90f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 26f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
@ -110,6 +114,7 @@ object Preferences : KotprefModel() {
var customFontName by stringPref(default = "")
var customFontVariant by stringPref(default = "regular")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showNextEventOnMultipleLines by booleanPref(default = false)
var showDividers by booleanPref(default = true)
@ -148,6 +153,7 @@ object Preferences : KotprefModel() {
var appNotificationsFilter by stringPref(default = "")
var showEventsAsGlanceProvider by booleanPref(default = false)
var showWeatherAsGlanceProvider by booleanPref(default = false)
// Integrations
var installedIntegrations by intPref(default = 0)

View File

@ -44,7 +44,6 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, 0))
setExact(
AlarmManager.RTC,
trigger,
@ -58,5 +57,14 @@ object AlarmHelper {
}
}
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, 0))
}
}
private const val ALARM_UPDATE_ID = 24953
}

View File

@ -1,10 +1,20 @@
package com.tommasoberlose.anotherwidget.helpers
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color
import android.util.Log
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toast
import kotlin.math.roundToInt
object ColorHelper {
fun getFontColor(isDark: Boolean): Int {
return try {
@ -144,4 +154,40 @@ object ColorHelper {
false
}
}
@SuppressLint("DefaultLocale")
fun Context.copyToClipboard(color: Int?, alpha: Int) {
if (color == null) return toast(getString(R.string.error_copy_color))
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
try {
val colorString = Integer.toHexString(color)
val clip = "#%s%s".format(
alpha.toHexValue(),
if (colorString.length > 6) colorString.substring(2) else colorString
).toUpperCase()
setPrimaryClip(ClipData.newPlainText(clip, clip))
toast(getString(R.string.color_copied))
} catch (ex: Exception) {
ex.printStackTrace()
toast(getString(R.string.error_copy_color))
}
}
}
fun Context.isClipboardColor(): Boolean {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
return try { primaryClip?.getItemAt(0)?.text?.toString()?.isColor() ?: false } catch (ex: Exception) { false }
}
}
fun Context.pasteFromClipboard(pasteColor: (color: String, alpha: String) -> Unit) {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
primaryClip?.let {
val item = it.getItemAt(0).text.toString().replace("#", "")
val color = if (item.length > 6) item.substring(2) else item
val alpha = if (item.length > 6) item.substring(0, 2) else "00"
pasteColor("#$color", alpha)
}
}
}
}

View File

@ -90,6 +90,12 @@ object GlanceProviderHelper {
R.drawable.round_event_note_24
)
}
Constants.GlanceProviderId.WEATHER -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_weather_as_glance_provider_title),
R.drawable.round_brightness_5_24
)
}
}
}
@ -108,10 +114,11 @@ object GlanceProviderHelper {
(MediaPlayerHelper.isSomeonePlaying(context)) ||
(Preferences.showBatteryCharging && Preferences.isCharging || Preferences.isBatteryLevelLow) ||
(Preferences.customNotes.isNotEmpty()) ||
(Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") ||
(Preferences.showDailySteps && Preferences.googleFitSteps > 0) ||
(Preferences.showGreetings && GreetingsHelper.showGreetings()) ||
(Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && eventRepository.getNextEvent() != null)
(Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && eventRepository.getNextEvent() != null)
)
eventRepository.close()
return showGlance

View File

@ -73,6 +73,9 @@ object ImageHelper {
allocationIn.destroy()
allocationOut.destroy()
colorMatrixScript.destroy()
blurScript.destroy()
//rs.destroy()
return bitmap
}

View File

@ -1,5 +1,6 @@
package com.tommasoberlose.anotherwidget.helpers
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.ContentUris
@ -27,6 +28,13 @@ object IntentHelper {
const val DO_NOTHING_OPTION = "DO_NOTHING"
const val REFRESH_WIDGET_OPTION = "REFRESH_WIDGET"
fun getPendingIntent(context: Context, requestCode: Int, intent: Intent, flags: Int): PendingIntent {
return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
PendingIntent.getActivity(context, requestCode, intent, flags)
else
PendingIntent.getBroadcast(context, requestCode, intent, 0)
}
fun getWidgetUpdateIntent(context: Context): Intent {
val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java)
@ -40,21 +48,19 @@ object IntentHelper {
private fun getWidgetRefreshIntent(context: Context): Intent {
return Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_REFRESH
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
fun getGoogleMapsIntentFromAddress(context: Context, address: String): Intent {
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=$address")
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=${Uri.encode(address)}")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.`package` = "com.google.android.apps.maps"
//mapIntent.`package` = "com.google.android.apps.maps"
return if (mapIntent.resolveActivity(context.packageManager) != null) {
mapIntent
mapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} else {
val map = "http://maps.google.co.in/maps?q=$address"
val i = Intent(Intent.ACTION_VIEW, Uri.parse(map));
i
val map = "https://www.google.com/maps/search/?api=1&query=${Uri.encode(address)}"
Intent(Intent.ACTION_VIEW, Uri.parse(map)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
@ -62,7 +68,6 @@ object IntentHelper {
return when (Preferences.weatherAppPackage) {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
@ -89,15 +94,16 @@ object IntentHelper {
}
}
fun getCalendarIntent(context: Context): Intent {
fun getCalendarIntent(context: Context, time: Long? = null): Intent {
val calendarUri = CalendarContract.CONTENT_URI
.buildUpon()
.appendPath("time")
.appendPath(Calendar.getInstance().timeInMillis.toString())
.appendPath((time ?: Calendar.getInstance().timeInMillis).toString())
.build()
return when (Preferences.calendarAppPackage) {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = calendarUri
}
}
@ -111,6 +117,8 @@ object IntentHelper {
val pm: PackageManager = context.packageManager
try {
pm.getLaunchIntentForPackage(Preferences.calendarAppPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Intent.ACTION_VIEW
data = calendarUri
}
@ -177,7 +185,7 @@ object IntentHelper {
}
}
false -> {
getCalendarIntent(context)
getCalendarIntent(context, e.startDate)
}
}
}
@ -209,7 +217,7 @@ object IntentHelper {
}
fun getBatteryIntent(): Intent {
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY)
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
fun getMusicIntent(context: Context): Intent {
@ -222,6 +230,7 @@ object IntentHelper {
try {
pm.getLaunchIntentForPackage(Preferences.mediaPlayerPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
@ -235,6 +244,7 @@ object IntentHelper {
return try {
pm.getLaunchIntentForPackage("com.google.android.apps.fitness")!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
@ -246,6 +256,7 @@ object IntentHelper {
return try {
pm.getLaunchIntentForPackage(Preferences.lastNotificationPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()

View File

@ -12,6 +12,7 @@ import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.NotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import java.lang.Exception
object MediaPlayerHelper {
@ -69,15 +70,24 @@ object MediaPlayerHelper {
isSomeonePlaying = true
if (metadata != null) {
Preferences.bulk {
mediaPlayerTitle =
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)?.toString()
?: ""
mediaPlayerArtist =
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)?.toString()
?: ""
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)?.toString()
?: ""
ignoreExceptions {
mediaPlayerTitle =
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)
?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerArtist =
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)
?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)
?.toString()
?: ""
}
}
}

View File

@ -88,19 +88,17 @@ object SettingsStringHelper {
fun getDifferenceText(context: Context, now: Long, start: Long): String {
val nowDate = DateTime(now)
val eventDate = DateTime(start)
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
val difference = start - now
when {
difference <= 0 -> {
return ""
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 15 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.LOW.rawValue -> {
return context.getString(R.string.soon)
@ -109,22 +107,7 @@ object SettingsStringHelper {
return context.getString(R.string.now)
}
TimeUnit.MILLISECONDS.toHours(difference) < 12 -> {
val minutes = TimeUnit.MILLISECONDS.toMinutes(difference) - 60 * TimeUnit.MILLISECONDS.toHours(difference)
return if (minutes < 1 || minutes > 30) {
DateUtils.getRelativeTimeSpanString(
start,
now - 1000 * 60 * 40,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
} else {
DateUtils.getRelativeTimeSpanString(
start,
now,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
}
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.HOUR_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
eventDate.dayOfYear == nowDate.plusDays(1).dayOfYear -> {
return String.format("%s", context.getString(R.string.tomorrow))
@ -143,9 +126,6 @@ object SettingsStringHelper {
val nowDate = DateTime(now)
val eventDate = DateTime(start)
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
return when (eventDate.dayOfYear) {
nowDate.dayOfYear -> {
""

View File

@ -21,11 +21,20 @@ object WeatherHelper {
suspend fun updateWeather(context: Context) {
Kotpref.init(context)
val networkApi = WeatherNetworkApi(context)
if (Preferences.customLocationAdd != "") {
networkApi.updateWeather()
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
WeatherNetworkApi(context).updateWeather()
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
context.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
LocationService.requestNewLocation(context)
} else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
removeWeather(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
}
}
@ -313,6 +322,23 @@ object WeatherHelper {
}
}
fun getWeatherLabel(context: Context, icon: String): String {
return when (icon) {
"01d", "01n" -> context.getString(R.string.weather_label_clear)
"02d", "02n" -> context.getString(R.string.weather_label_partly_cloudy)
"03d", "03n" -> context.getString(R.string.weather_label_mostly_cloudy)
"04d", "04n" -> context.getString(R.string.weather_label_cloudy_weather)
"09d", "09n" -> context.getString(R.string.weather_label_storm_weather)
"10d", "10n" -> context.getString(R.string.weather_label_rainy)
"11d", "11n" -> context.getString(R.string.weather_label_thunder)
"13d", "13n" -> context.getString(R.string.weather_label_snow)
"50d", "50n", "82d", "82n" -> context.getString(R.string.weather_label_haze)
"80d", "80n" -> context.getString(R.string.weather_label_windy)
"81d", "81n" -> context.getString(R.string.weather_label_rain_snow)
else -> context.getString(R.string.weather_label_unknown)
}
}
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when {
iconString.contains("skc") -> "01"
iconString.contains("few") -> "02"

View File

@ -63,21 +63,23 @@ object WidgetHelper {
R.array.com_google_android_gms_fonts_certs
)
val handlerThread = HandlerThread("generateView")
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
handlerThread.quit()
function.invoke(typeface)
}
override fun onTypefaceRequestFailed(reason: Int) {
handlerThread.quit()
function.invoke(null)
}
}
val handlerThread = HandlerThread("generateView")
handlerThread.start()
if (Looper.myLooper() == null) {
Looper.prepare()
}
//if (Looper.myLooper() == null) {
// Looper.prepare()
//}
Handler(handlerThread.looper).run {
FontsContractCompat.requestFont(context, request, callback, this)

View File

@ -42,6 +42,13 @@ class WeatherNetworkApi(val context: Context) {
Constants.WeatherProvider.YR -> useYrProvider(context)
}
} else {
if (!Preferences.showWeather)
Preferences.weatherProviderError = context.getString(R.string.show_weather_not_visible)
else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
}
WeatherHelper.removeWeather(
context
)

View File

@ -13,8 +13,7 @@ class NewCalendarEventReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val eventRepository = EventRepository(context)
when (intent.action) {
Intent.ACTION_PROVIDER_CHANGED,
Intent.ACTION_TIME_CHANGED -> {
Intent.ACTION_PROVIDER_CHANGED -> {
CalendarHelper.updateEventList(context)
}
Actions.ACTION_GO_TO_NEXT_EVENT -> {

View File

@ -38,6 +38,7 @@ class UpdatesReceiver : BroadcastReceiver() {
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS,
Actions.ACTION_TIME_UPDATE -> {
MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) {
@ -49,9 +50,6 @@ class UpdatesReceiver : BroadcastReceiver() {
ActiveNotificationsHelper.clearLastNotification(context)
MainWidget.updateWidget(context)
}
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context)
}
Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
@ -67,9 +65,31 @@ class UpdatesReceiver : BroadcastReceiver() {
const val EVENT_ID = "EVENT_ID"
fun setUpdates(context: Context, eventId: Long? = null) {
if (!Preferences.showEvents)
return
val eventRepository = EventRepository(context)
if (eventId == null) {
removeUpdates(context)
// schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact(
AlarmManager.RTC,
Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis,
PendingIntent.getBroadcast(
context,
0,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
},
0
)
)
}
eventRepository.getFutureEvents().forEach { event ->
setEventUpdate(context, event)
@ -84,109 +104,90 @@ class UpdatesReceiver : BroadcastReceiver() {
}
private fun setEventUpdate(context: Context, event: Event) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate)
val limit = when (Preferences.showUntil) {
0 -> 1000 * 60 * 60 * 3
1 -> 1000 * 60 * 60 * 6
2 -> 1000 * 60 * 60 * 12
3 -> 1000 * 60 * 60 * 24
4 -> 1000 * 60 * 60 * 24 * 3
5 -> 1000 * 60 * 60 * 24 * 7
6 -> 1000 * 60 * 30
7 -> 1000 * 60 * 60
else -> 1000 * 60 * 60 * 6
}
if (event.startDate <= limit) {
if (event.startDate > now.timeInMillis) {
// Update the widget every hour till the event
if (diff.hours == 0) {
var minutes = 0
when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
minutes = when {
diff.minutes > 50 -> 50
diff.minutes > 30 -> 30
diff.minutes > 15 -> 15
else -> 0
}
}
Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
minutes = diff.minutes - (diff.minutes % 5)
}
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate, org.joda.time.PeriodType.time())
val limit = when (Preferences.showUntil) {
0 -> 1000 * 60 * 60 * 3
1 -> 1000 * 60 * 60 * 6
2 -> 1000 * 60 * 60 * 12
3 -> 1000 * 60 * 60 * 24
4 -> 1000 * 60 * 60 * 24 * 3
5 -> 1000 * 60 * 60 * 24 * 7
6 -> 1000 * 60 * 30
7 -> 1000 * 60 * 60
else -> 1000 * 60 * 60 * 6
}
val fireTime = when {
event.startDate <= now.timeInMillis
-> event.endDate
event.startDate > now.timeInMillis + limit
-> event.startDate - limit
!Preferences.showDiffTime
-> return
event.allDay
-> event.startDate
diff.hours > 12
-> event.startDate - 12 * 1000 * 60 * 60 + 1000 * 60
diff.hours > 0
-> event.startDate - diff.hours * 1000 * 60 * 60 + 1000 * 60
else
-> event.startDate - 1000 * 60 * when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
when {
diff.minutes >= 45 -> 44
diff.minutes >= 30 -> 29
diff.minutes >= 15 -> 14
else -> 0
}
setExact(
AlarmManager.RTC,
if (event.startDate - minutes * 1000 * 60 > (now.timeInMillis + 120 * 1000)) event.startDate - 60 * 1000 * minutes else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
} else {
setExact(
AlarmManager.RTC,
event.startDate - diff.hours * 1000 * 60 * 60 + if (diff.minutes > 30) (-30) else (+30),
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
} else {
// Update the widget one second after the event is finished
val fireTime =
if (event.endDate > now.timeInMillis + 120 * 1000) event.endDate else now.timeInMillis + 120000
setExact(
AlarmManager.RTC,
fireTime,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
},
0
)
)
Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
when {
diff.minutes >= 5 -> diff.minutes - diff.minutes % 5 - 1
else -> 0
}
}
else -> 0
}
} else {
setExact(
AlarmManager.RTC,
if (event.startDate - limit > now.timeInMillis + 120 * 1000) event.startDate - limit else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
}
// no need to schedule updates after the next ACTION_CALENDAR_UPDATE
if (Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis <= fireTime) return
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact(
AlarmManager.RTC,
fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60),
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
if (event.startDate > now.timeInMillis)
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
}
)
}
}
fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
}, 0))
val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java), 0))
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
}, 0))
}
eventRepository.close()
}

View File

@ -22,21 +22,14 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED -> setUpdates(context)
Actions.ACTION_WEATHER_UPDATE -> {
GlobalScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(context)
}
}
Intent.ACTION_TIME_CHANGED,
Actions.ACTION_WEATHER_UPDATE -> setUpdates(context)
}
}
companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) {
removeUpdates(context)
if (Preferences.showWeather) {
val interval = MINUTE * when (Preferences.weatherRefreshPeriod) {
0 -> 30
@ -48,13 +41,15 @@ class WeatherReceiver : BroadcastReceiver() {
else -> 60
}
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setRepeating(
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis,
interval,
System.currentTimeMillis() + interval,
PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
GlobalScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(context)
}
}
}

View File

@ -16,14 +16,15 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) {
try {
if (Preferences.weatherAppPackage == IntentHelper.REFRESH_WIDGET_OPTION) {
context.sendBroadcast(IntentHelper.getWeatherIntent(context))
} else {
context.startActivity(IntentHelper.getWeatherIntent(context))
IntentHelper.getWeatherIntent(context).run {
if (flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(this)
else
context.sendBroadcast(this)
}
} catch (e: Exception) {
e.printStackTrace()
val uri = Uri.parse("http://www.google.com/search?q=weather")
val uri = Uri.parse("https://www.google.com/search?q=weather")
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try {

View File

@ -18,6 +18,7 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
@ -37,12 +38,31 @@ class LocationService : Service() {
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
if (ActivityCompat.checkSelfPermission(
this@LocationService,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
if (checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation.addOnCompleteListener { task ->
if (com.google.android.gms.common.GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this@LocationService)
== com.google.android.gms.common.ConnectionResult.SUCCESS
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation
} else {
val lm = getSystemService(LOCATION_SERVICE) as android.location.LocationManager
var location: android.location.Location? = null
for (provider in arrayOf(
"fused", // LocationManager.FUSED_PROVIDER,
android.location.LocationManager.GPS_PROVIDER,
android.location.LocationManager.NETWORK_PROVIDER,
android.location.LocationManager.PASSIVE_PROVIDER
)) {
if (lm.isProviderEnabled(provider)) {
location = lm.getLastKnownLocation(provider)
if (location != null) break
}
}
com.google.android.gms.tasks.Tasks.forResult(location)
}.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result

View File

@ -55,18 +55,20 @@ class UpdateCalendarService : Service() {
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
UpdatesReceiver.removeUpdates(this@UpdateCalendarService)
val eventRepository = EventRepository(this@UpdateCalendarService)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
val begin = Calendar.getInstance().apply {
val limit = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
}
val limit = Calendar.getInstance().apply {
add(Calendar.DATE, 1)
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
@ -85,29 +87,37 @@ class UpdateCalendarService : Service() {
)
) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
Preferences.showEvents = false
} else {
try {
val provider = CalendarProvider(this@UpdateCalendarService)
val data = provider.getInstances(begin.timeInMillis, limit.timeInMillis)
// apply time zone offset to correctly fetch all-day events
val data = provider.getInstances(
now.timeInMillis + now.timeZone.getOffset(now.timeInMillis).coerceAtMost(0),
limit.timeInMillis + limit.timeZone.getOffset(limit.timeInMillis).coerceAtLeast(0)
)
if (data != null) {
val instances = data.list
for (instance in instances) {
try {
val e = provider.getEvent(instance.eventId)
if (e != null && !e.deleted && instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end && !CalendarHelper.getFilteredCalendarIdList()
.contains(e.calendarId)
) {
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (e == null || e.deleted || CalendarHelper.getFilteredCalendarIdList().contains(e.calendarId))
continue
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end) {
/* Following check may result in "fake" all-day events with
* non-UTC start/end time, and therefore cannot be found by
* Calendar when tapped to open details.
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
@ -124,6 +134,7 @@ class UpdateCalendarService : Service() {
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
*/
eventList.add(
Event(
@ -133,7 +144,7 @@ class UpdateCalendarService : Service() {
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId.toInt(),
allDay = isAllDay,
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
@ -164,13 +175,14 @@ class UpdateCalendarService : Service() {
}
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
}
eventRepository.close()
UpdatesReceiver.setUpdates(this@UpdateCalendarService)
MainWidget.updateWidget(this@UpdateCalendarService)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
eventRepository.close()
stopSelf()
}

View File

@ -140,6 +140,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
Preferences.showEvents = false
com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver.removeUpdates(this)
}
}

View File

@ -38,12 +38,15 @@ class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel
private lateinit var binding: ActivityCustomFontBinding
private lateinit var handlerThread: HandlerThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
binding = ActivityCustomFontBinding.inflate(layoutInflater)
handlerThread = HandlerThread("listCustomFonts")
handlerThread.start()
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
@ -64,14 +67,17 @@ class CustomFontActivity : AppCompatActivity() {
injector
.text(R.id.text, item)
.with<TextView>(R.id.text) {
val googleSans: Typeface = when (Preferences.customFontVariant) {
"100" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_thin.ttf")
"200" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_light.ttf")
"500" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_medium.ttf")
"700" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_bold.ttf")
"800" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_black.ttf")
else -> Typeface.createFromAsset(this.assets, "fonts/google_sans_regular.ttf")
}
val googleSans: Typeface? = androidx.core.content.res.ResourcesCompat.getFont(
this,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
}
)
it.typeface = googleSans
}
@ -97,32 +103,49 @@ class CustomFontActivity : AppCompatActivity() {
)
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
it.typeface = typeface
it.isVisible = true
class Callback : FontsContractCompat.FontRequestCallback() {
var handler: Handler? = Handler(handlerThread.looper)
it.measure(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
fun cancel() {
if (handler != null) {
handler!!.removeCallbacksAndMessages(null)
handler = null
}
}
protected fun finalize() {
cancel()
}
override fun onTypefaceRetrieved(typeface: Typeface) {
if (it.tag == this) {
it.tag = null
it.typeface = typeface
it.setTextColor(getColor(R.color.colorPrimaryText))
}
}
override fun onTypefaceRequestFailed(reason: Int) {
it.isVisible = false
it.layoutParams = it.layoutParams.apply {
height = 0
if (it.tag == this) {
it.tag = null
//it.text = item.fontFamily + " ($reason)"
it.setTextColor(getColor(R.color.errorColorText))
}
}
}
val handlerThread = HandlerThread(item.fontFamily)
handlerThread.start()
val mHandler = Handler(handlerThread.looper)
(it.tag as Callback?)?.cancel()
val callback = Callback()
it.tag = callback
it.typeface = null
it.setTextColor(getColor(R.color.colorSecondaryText))
val mHandler = callback.handler!!
FontsContractCompat.requestFont(this, request, callback, mHandler)
}
injector.clicked(R.id.text) {
if ((it as TextView).typeface == null) return@clicked
val dialog = BottomSheetMenu<Int>(this, header = item.fontFamily)
if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
@ -147,6 +170,12 @@ class CustomFontActivity : AppCompatActivity() {
setContentView(binding.root)
}
override fun onDestroy() {
handlerThread.quit()
filterJob?.cancel()
super.onDestroy()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityCustomFontBinding, viewModel: CustomFontViewModel) {
@ -204,6 +233,13 @@ class CustomFontActivity : AppCompatActivity() {
adapter.updateData(filteredList)
binding.loader.visibility = View.INVISIBLE
}
} else {
delay(200)
withContext(Dispatchers.Main) {
adapter.updateData(listOf(getString(R.string.custom_font_subtitle_1)).filter {
it.contains(search ?: "", ignoreCase = true)
})
}
}
}
}

View File

@ -46,22 +46,27 @@ class CustomLocationActivity : AppCompatActivity() {
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_location_item) { _, injector ->
injector
.text(R.id.text, getString(R.string.custom_location_gps))
.clicked(R.id.text) {
requirePermission()
injector.text(R.id.text, getString(R.string.custom_location_gps))
injector.clicked(R.id.text) {
Preferences.bulk {
remove(Preferences::customLocationLat)
remove(Preferences::customLocationLon)
remove(Preferences::customLocationAdd)
}
setResult(Activity.RESULT_OK)
finish()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
injector.text(R.id.text, item.getAddressLine(0))
injector.text(R.id.text, item.getAddressLine(0) ?: "")
injector.clicked(R.id.item) {
Preferences.bulk {
customLocationLat = item.latitude.toString()
customLocationLon = item.longitude.toString()
customLocationAdd = item.getAddressLine(0)
setResult(Activity.RESULT_OK)
finish()
customLocationAdd = item.getAddressLine(0) ?: ""
}
setResult(Activity.RESULT_OK)
finish()
}
}
.attachTo(binding.listView)
@ -115,36 +120,6 @@ class CustomLocationActivity : AppCompatActivity() {
})
}
private fun requirePermission() {
Dexter.withContext(this)
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
Preferences.bulk {
remove(Preferences::customLocationLat)
remove(Preferences::customLocationLon)
remove(Preferences::customLocationAdd)
}
setResult(Activity.RESULT_OK)
finish()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
}
})
.check()
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()

View File

@ -74,7 +74,11 @@ class TimeZoneSelectorActivity : AppCompatActivity() {
if (id != null) {
Preferences.bulk {
altTimezoneId = id
altTimezoneLabel = item.locality
altTimezoneLabel = try {
item.locality
} catch (ex: Exception) {
item.getAddressLine(0)
}
}
MainWidget.updateWidget(this@TimeZoneSelectorActivity)
setResult(Activity.RESULT_OK)

View File

@ -127,11 +127,11 @@ class WeatherProviderActivity : AppCompatActivity() {
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
updateListItem()
binding.listView.postDelayed({ updateListItem() }, 300)
}
viewModel.weatherProviderLocationError.observe(this) {
updateListItem()
binding.listView.postDelayed({ updateListItem() }, 300)
}
}

View File

@ -8,15 +8,10 @@ import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
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.LinearLayout
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import android.widget.RemoteViews
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -29,23 +24,20 @@ import com.tommasoberlose.anotherwidget.databinding.FragmentAppMainBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.ui.widgets.StandardWidget
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
private val PREVIEW_BASE_HEIGHT: Int
get() = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) 120 else 180
}
private lateinit var viewModel: MainViewModel
@ -97,13 +89,7 @@ class MainFragment : Fragment() {
}
binding.actionSettings.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment,)
}
binding.preview.layoutParams = binding.preview.layoutParams.apply {
height = PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment)
}
subscribeUi(viewModel)
@ -155,186 +141,52 @@ class MainFragment : Fragment() {
binding.toolbar.cardElevation = if (it > 0) 24f else 0f
}
viewModel.widgetAlign.observe(viewLifecycleOwner) {
updatePreviewVisibility()
lifecycleScope.launch {
delay(350)
updateClock()
}
}
viewModel.showPreview.observe(viewLifecycleOwner) {
updatePreviewVisibility()
}
viewModel.clockPreferencesUpdate.observe(viewLifecycleOwner) {
updateClock()
}
viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) {
onUpdateUiEvent(null)
}
viewModel.showClock.observe(viewLifecycleOwner) {
updateClockVisibility(it)
}
}
private var uiJob: Job? = null
private fun updateUI() {
uiJob?.cancel()
if (Preferences.showPreview) {
lifecycleScope.launch(Dispatchers.IO) {
val bgColor: Int = ContextCompat.getColor(
requireContext(),
if (ColorHelper.getFontColor(requireActivity().isDarkTheme())
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
val wallpaperDrawable = BitmapHelper.getTintedDrawable(
requireContext(),
R.drawable.card_background,
ColorHelper.getBackgroundColor(requireActivity().isDarkTheme())
)
withContext(Dispatchers.Main) {
binding.preview.setCardBackgroundColor(bgColor)
binding.widgetDetail.widgetShapeBackground.setImageDrawable(wallpaperDrawable)
}
}
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob?.cancel()
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.getWidgetView(requireContext(), typeface).root
val generatedView = MainWidget.getWidgetView(requireContext(), binding.widget.width, typeface)
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
binding.preview.measure(0, 0)
}
if (generatedView != null) {
withContext(Dispatchers.Main) {
val view: View = generatedView.apply(requireActivity().applicationContext, binding.widget)
view.measure(0, 0)
val bitmap = BitmapHelper.getBitmapFromView(
generatedView,
if (binding.preview.width > 0) binding.preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
binding.widgetLoader.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(200L).start()
binding.widget.animate().alpha(0f).setDuration(200L).withEndAction {
binding.widget.removeAllViews()
binding.widget.addView(view)
withContext(Dispatchers.Main) {
binding.widgetDetail.bitmapContainer.apply {
setImageBitmap(bitmap)
updatePreviewVisibility(view.measuredHeight)
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.widget.animate().alpha(1f).start()
}.start()
}
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.widget.animate().alpha(1f).start()
}
}
}
}
}
private fun updateClock() {
// Clock
binding.widgetDetail.time.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.timeAmPm.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.time.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
binding.widgetDetail.timeAmPm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
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
binding.widgetDetail.clockBottomMarginSmall.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.rawValue
binding.widgetDetail.clockBottomMarginMedium.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.rawValue
binding.widgetDetail.clockBottomMarginLarge.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.rawValue
// 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
}
}
private fun updateClockVisibility(showClock: Boolean) {
binding.widgetDetail.timeContainer.clearAnimation()
binding.widgetDetail.time.clearAnimation()
updatePreviewVisibility()
if (showClock) {
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
binding.widgetDetail.timeContainer.measure(0, 0)
}
if ((Preferences.showClock && binding.widgetDetail.time.alpha != 1f) || (!Preferences.showClock && binding.widgetDetail.time.alpha != 0f)) {
val initialHeight = binding.widgetDetail.timeContainer.measuredHeight
ValueAnimator.ofFloat(
if (showClock) 0f else 1f,
if (showClock) 1f else 0f
).apply {
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Float
binding.widgetDetail.timeContainer.layoutParams =
binding.widgetDetail.timeContainer.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
binding.widgetDetail.time.alpha = animatedValue
}
}.start()
}
}
private fun updatePreviewVisibility() {
binding.preview.clearAnimation()
if (binding.preview.layoutParams.height != (if (Preferences.showPreview) PREVIEW_BASE_HEIGHT.toPixel(requireContext()) else 0) + (if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0)) {
private fun updatePreviewVisibility(widgetHeight: Int) {
val newHeight = widgetHeight + 32f.convertDpToPixel(requireContext()).toInt()
if (binding.preview.layoutParams.height != newHeight) {
binding.preview.clearAnimation()
ValueAnimator.ofInt(
binding.preview.height,
(if (Preferences.showPreview) PREVIEW_BASE_HEIGHT.toPixel(requireContext()) else 0) + (if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0)
newHeight
).apply {
duration = 300L
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams

View File

@ -35,6 +35,7 @@ import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.settings.SupportDevActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.setOnSingleClickListener
import kotlinx.coroutines.Dispatchers
@ -206,10 +207,14 @@ class SettingsFragment : Fragment() {
.rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
.withEndAction {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
try {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
.start()

View File

@ -63,6 +63,7 @@ class CalendarFragment : Fragment() {
binding.showAllDayToggle.setCheckedImmediatelyNoEvent(Preferences.calendarAllDay)
binding.showOnlyBusyEventsToggle.setCheckedImmediatelyNoEvent(Preferences.showOnlyBusyEvents)
binding.showDiffTimeToggle.setCheckedImmediatelyNoEvent(Preferences.showDiffTime)
binding.showNextEventOnMultipleLinesToggle.setCheckedImmediatelyNoEvent(Preferences.showNextEventOnMultipleLines)
setupListener()
@ -90,6 +91,12 @@ class CalendarFragment : Fragment() {
}
}
viewModel.showNextEventOnMultipleLines.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showNextEventOnMultipleLinesLabel.text = if (it) getString(R.string.settings_enabled) else getString(R.string.settings_disabled)
}
}
viewModel.showDiffTime.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showDiffTimeLabel.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
@ -175,7 +182,7 @@ class CalendarFragment : Fragment() {
binding.showAllDayToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.calendarAllDay = isChecked
MainWidget.updateWidget(requireContext())
updateCalendar()
}
binding.actionChangeAttendeeFilter.setOnClickListener {
@ -220,7 +227,7 @@ class CalendarFragment : Fragment() {
binding.showOnlyBusyEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showOnlyBusyEvents = isChecked
MainWidget.updateWidget(requireContext())
updateCalendar()
}
binding.actionShowDiffTime.setOnClickListener {
@ -231,6 +238,14 @@ class CalendarFragment : Fragment() {
Preferences.showDiffTime = isChecked
}
binding.actionShowNextEventOnMultipleLines.setOnClickListener {
binding.showNextEventOnMultipleLinesToggle.isChecked = !binding.showNextEventOnMultipleLinesToggle.isChecked
}
binding.showNextEventOnMultipleLinesToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showNextEventOnMultipleLines = isChecked
}
binding.actionWidgetUpdateFrequency.setOnClickListener {
if (Preferences.showEvents && Preferences.showDiffTime) {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_widget_update_frequency_title), message = getString(R.string.settings_widget_update_frequency_subtitle)).setSelectedValue(Preferences.widgetUpdateFrequency)
@ -239,6 +254,7 @@ class CalendarFragment : Fragment() {
.addItem(getString(R.string.settings_widget_update_frequency_low), Constants.WidgetUpdateFrequency.LOW.rawValue)
.addOnSelectItemListener { value ->
Preferences.widgetUpdateFrequency = value
updateCalendar()
}.show()
}
}

View File

@ -17,7 +17,7 @@ import com.chibatching.kotpref.bulk
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.BottomSheetPicker
import com.tommasoberlose.anotherwidget.databinding.FragmentTabClockBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
@ -144,16 +144,15 @@ class ClockFragment : Fragment() {
private fun setupListener() {
binding.actionClockTextSize.setOnClickListener {
val dialog = BottomSheetMenu<Float>(
BottomSheetPicker(
requireContext(),
header = getString(R.string.settings_clock_text_size_title)
).setSelectedValue(Preferences.clockTextSize)
(46 downTo 12).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.clockTextSize = value
}.show()
items = (46 downTo 12).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.clockTextSize },
header = getString(R.string.settings_clock_text_size_title),
onItemSelected = {value ->
if (value != null) Preferences.clockTextSize = value
}
).show()
}
binding.actionAltTimezoneClock.setOnClickListener {

View File

@ -102,15 +102,7 @@ class GesturesFragment : Fragment() {
it == IntentHelper.DO_NOTHING_OPTION -> getString(R.string.gestures_do_nothing)
it == IntentHelper.REFRESH_WIDGET_OPTION -> getString(R.string.gestures_refresh_widget)
it != IntentHelper.DEFAULT_OPTION -> it
else -> {
if (IntentHelper.getCalendarIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_calendar_app
)
} else {
getString(R.string.gestures_do_nothing)
}
}
else -> getString(R.string.default_calendar_app)
}
}
}
@ -127,15 +119,7 @@ class GesturesFragment : Fragment() {
it == IntentHelper.DO_NOTHING_OPTION -> getString(R.string.gestures_do_nothing)
it == IntentHelper.REFRESH_WIDGET_OPTION -> getString(R.string.gestures_refresh_widget)
it != IntentHelper.DEFAULT_OPTION -> it
else -> {
if (IntentHelper.getClockIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_clock_app
)
} else {
getString(R.string.gestures_do_nothing)
}
}
else -> getString(R.string.default_clock_app)
}
}
}
@ -173,9 +157,12 @@ class GesturesFragment : Fragment() {
}
binding.actionCalendarApp.setOnClickListener {
startActivityForResult(Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.calendarAppPackage)
}, RequestCode.CALENDAR_APP_REQUEST_CODE.code)
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.calendarAppPackage)
},
RequestCode.CALENDAR_APP_REQUEST_CODE.code
)
}
binding.actionClockApp.setOnClickListener {

View File

@ -293,6 +293,25 @@ class GlanceTabFragment : Fragment() {
if (!(isVisible && hasError)) View.VISIBLE else View.GONE
)
}
Constants.GlanceProviderId.WEATHER -> {
isVisible =
Preferences.showWeatherAsGlanceProvider
val hasError = !Preferences.showWeather || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || Preferences.weatherProviderLocationError != ""
injector.text(
R.id.label,
if (isVisible && !hasError) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(
R.id.error_icon,
if (isVisible && hasError) View.VISIBLE else View.GONE
)
injector.visibility(
R.id.info_icon,
if (!(isVisible && hasError)) View.VISIBLE else View.GONE
)
}
}
injector.alpha(R.id.title, if (isVisible) 1f else .25f)

View File

@ -86,6 +86,28 @@ class LayoutFragment : Fragment() {
viewModel: MainViewModel
) {
viewModel.widgetMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.widgetMarginLabel.text = when (it) {
Constants.Dimension.NONE.rawValue -> getString(R.string.settings_widget_dim_none)
Constants.Dimension.SMALL.rawValue -> getString(R.string.settings_widget_dim_small)
Constants.Dimension.LARGE.rawValue -> getString(R.string.settings_widget_dim_large)
else -> getString(R.string.settings_widget_dim_medium)
}
}
}
viewModel.widgetPadding.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.widgetPaddingLabel.text = when (it) {
Constants.Dimension.NONE.rawValue -> getString(R.string.settings_widget_dim_none)
Constants.Dimension.SMALL.rawValue -> getString(R.string.settings_widget_dim_small)
Constants.Dimension.LARGE.rawValue -> getString(R.string.settings_widget_dim_large)
else -> getString(R.string.settings_widget_dim_medium)
}
}
}
viewModel.secondRowTopMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.secondRowTopMarginLabel.text = when (it) {
@ -101,12 +123,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)
}
@ -145,6 +169,58 @@ class LayoutFragment : Fragment() {
private fun setupListener() {
binding.actionWidgetMargin.setOnClickListener {
BottomSheetMenu<Float>(
requireContext(),
header = getString(R.string.settings_widget_margin_title)
).setSelectedValue(Preferences.widgetMargin)
.addItem(
getString(R.string.settings_widget_dim_none),
Constants.Dimension.NONE.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_small),
Constants.Dimension.SMALL.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_medium),
Constants.Dimension.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_large),
Constants.Dimension.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetMargin = value
}.show()
}
binding.actionWidgetPadding.setOnClickListener {
BottomSheetMenu<Float>(
requireContext(),
header = getString(R.string.settings_widget_padding_title)
).setSelectedValue(Preferences.widgetPadding)
.addItem(
getString(R.string.settings_widget_dim_none),
Constants.Dimension.NONE.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_small),
Constants.Dimension.SMALL.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_medium),
Constants.Dimension.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_large),
Constants.Dimension.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetPadding = value
}.show()
}
binding.actionSecondRowTopMarginSize.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
@ -210,6 +286,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

@ -23,6 +23,7 @@ import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentPreferencesBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
@ -123,6 +124,7 @@ class PreferencesFragment : Fragment() {
requireCalendarPermission()
} else {
Preferences.showEvents = enabled
UpdatesReceiver.removeUpdates(requireContext())
}
}
@ -133,6 +135,8 @@ class PreferencesFragment : Fragment() {
binding.showWeatherSwitch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showWeather = enabled
if (enabled) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
WeatherReceiver.setUpdates(requireContext())
} else {
WeatherReceiver.removeUpdates(requireContext())

View File

@ -15,6 +15,7 @@ import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.BottomSheetPicker
import com.tommasoberlose.anotherwidget.databinding.FragmentTabTypographyBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
@ -167,25 +168,27 @@ class TypographyFragment : Fragment() {
private fun setupListener() {
binding.actionMainTextSize.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_main_text_size)).setSelectedValue(
Preferences.textMainSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textMainSize = value
}.show()
BottomSheetPicker(
requireContext(),
items = (40 downTo 10).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.textMainSize },
header = getString(R.string.title_main_text_size),
onItemSelected = {value ->
if (value != null) Preferences.textMainSize = value
}
).show()
}
binding.actionSecondTextSize.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_second_text_size)).setSelectedValue(
Preferences.textSecondSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textSecondSize = value
}.show()
BottomSheetPicker(
requireContext(),
items = (40 downTo 10).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.textSecondSize },
header = getString(R.string.title_second_text_size),
onItemSelected = {value ->
if (value != null) Preferences.textSecondSize = value
}
).show()
}
binding.actionFontColor.setOnClickListener {
@ -209,7 +212,7 @@ class TypographyFragment : Fragment() {
} else {
Preferences.textGlobalAlpha = alpha.toHexValue()
}
}
},
).show()
}
@ -236,7 +239,7 @@ class TypographyFragment : Fragment() {
} else {
Preferences.textSecondaryAlpha = alpha.toHexValue()
}
}
},
).show()
}
@ -273,7 +276,7 @@ class TypographyFragment : Fragment() {
Intent(requireContext(), CustomFontActivity::class.java),
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code
)
} else if (value != Constants.CUSTOM_FONT_DOWNLOADED) {
} else if (value != Preferences.customFont) {
Preferences.bulk {
customFont = value
customFontFile = ""

View File

@ -91,16 +91,17 @@ class WeatherFragment : Fragment() {
viewModel.weatherProvider.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherProvider.text = WeatherHelper.getProviderName(requireContext(), Constants.WeatherProvider.fromInt(it)!!)
checkWeatherProviderConfig()
}
}
viewModel.weatherProviderError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
checkLocationPermission()
}
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
checkLocationPermission()
}
viewModel.customLocationAdd.observe(viewLifecycleOwner) {
@ -108,6 +109,7 @@ class WeatherFragment : Fragment() {
binding.labelCustomLocation.text =
if (it == "") getString(R.string.custom_location_gps) else it
}
checkWeatherProviderConfig()
checkLocationPermission()
}
@ -116,43 +118,50 @@ class WeatherFragment : Fragment() {
binding.tempUnit.text =
if (it == "F") getString(R.string.fahrenheit) else getString(R.string.celsius)
}
checkLocationPermission()
}
viewModel.weatherRefreshPeriod.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherRefreshPeriod.text = getString(SettingsStringHelper.getRefreshPeriodString(it))
}
checkLocationPermission()
}
viewModel.weatherIconPack.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherIconPack.text = getString(R.string.settings_weather_icon_pack_default).format((it + 1))
}
checkLocationPermission()
}
}
private fun checkLocationPermission() {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
binding.locationPermissionAlert.isVisible = false
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
} else if (Preferences.customLocationAdd == "") {
binding.locationPermissionAlert.isVisible = true
binding.locationPermissionAlert.setOnClickListener {
requirePermission()
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)
) {
val text = getString(R.string.action_grant_permission) + " - " +
requireContext().packageManager.backgroundPermissionOptionLabel
binding.locationPermissionAlert.text = text
}
binding.weatherProviderLocationError.isVisible = false
} else {
binding.locationPermissionAlert.isVisible = false
}
}
private fun checkWeatherProviderConfig() {
binding.weatherProviderError.isVisible = Preferences.showWeather && Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.isVisible = Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.text = Preferences.weatherProviderError
binding.weatherProviderLocationError.isVisible = Preferences.showWeather && Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.isVisible = Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.text = Preferences.weatherProviderLocationError
}
@ -177,11 +186,11 @@ class WeatherFragment : Fragment() {
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
Preferences.weatherTempUnit = value
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
Preferences.weatherTempUnit = value
}.show()
}
@ -193,7 +202,10 @@ class WeatherFragment : Fragment() {
}
dialog
.addOnSelectItemListener { value ->
Preferences.weatherRefreshPeriod = value
if (value != Preferences.weatherRefreshPeriod) {
Preferences.weatherRefreshPeriod = value
WeatherReceiver.setUpdates(requireContext())
}
}.show()
}
@ -206,12 +218,12 @@ class WeatherFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
WeatherReceiver.setUpdates(requireContext())
checkLocationPermission()
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
checkLocationPermission()
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
//RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
//}
}
}
super.onActivityResult(requestCode, resultCode, data)
@ -220,12 +232,19 @@ class WeatherFragment : Fragment() {
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION))
Manifest.permission.ACCESS_BACKGROUND_LOCATION
else
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
if (report.areAllPermissionsGranted()) {
checkLocationPermission()
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
}
}

View File

@ -50,6 +50,8 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
val widgetAlign = Preferences.asLiveData(Preferences::widgetAlign)
val widgetMargin = Preferences.asLiveData(Preferences::widgetMargin)
val widgetPadding = Preferences.asLiveData(Preferences::widgetPadding)
// Calendar Settings
val showEvents = Preferences.asLiveData(Preferences::showEvents)
@ -57,6 +59,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
val showUntil = Preferences.asLiveData(Preferences::showUntil)
val showDiffTime = Preferences.asLiveData(Preferences::showDiffTime)
val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent)
val showNextEventOnMultipleLines = Preferences.asLiveData(Preferences::showNextEventOnMultipleLines)
val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails)
val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName)
val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency)
@ -101,7 +104,8 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
// UI
val fragmentScrollY = MutableLiveData<Int>()
val clockPreferencesUpdate = MediatorLiveData<Boolean>().apply {
val widgetPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::showClock)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextSize)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
@ -110,8 +114,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
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 }
addSource(Preferences.asLiveData(Preferences::textGlobalAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryColor)) { value = true }
@ -135,6 +137,8 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetAlign)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetPadding)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDividers)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowTopMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::isDateCapitalize)) { value = true }
@ -144,6 +148,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::calendarAllDay)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDiffTime)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNextEvent)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNextEventOnMultipleLines)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDeclinedEvents)) { value = true }
addSource(Preferences.asLiveData(Preferences::showInvitedEvents)) { value = true }
addSource(Preferences.asLiveData(Preferences::showAcceptedEvents)) { value = true }
@ -169,6 +174,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::musicPlayersFilter)) { value = true }
addSource(Preferences.asLiveData(Preferences::appNotificationsFilter)) { value = true }
addSource(Preferences.asLiveData(Preferences::showEventsAsGlanceProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeatherAsGlanceProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::installedIntegrations)) { value = true }
}

View File

@ -0,0 +1,995 @@
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 margin = Preferences.widgetMargin.convertDpToPixel(context).toInt()
views.setViewPadding(R.id.widget_shape_background, margin, margin, margin, margin)
val refreshIntent = IntentHelper.getPendingIntent(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
// Padding
val padding = (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt()
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} 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_rect, 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, width = bindingView.weatherDateLine.width, height = bindingView.weatherDateLine.height)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false, width = bindingView.weatherSubLine.width, height = bindingView.weatherSubLine.height)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false, width = bindingView.date.width, height = bindingView.date.height)
)
val calPIntent = IntentHelper.getPendingIntent(
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)
// 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, width = bindingView.actionNext.width, height = bindingView.actionNext.height)
)
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 = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false, width = bindingView.nextEvent.width, height = bindingView.nextEvent.height)
)
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,
width = bindingView.nextEventDifferenceTime.width,
height = bindingView.nextEventDifferenceTime.height
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
if (!Preferences.showNextEventOnMultipleLines) {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
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, if (Preferences.showWeather && Preferences.weatherIcon != "") View.VISIBLE else View.GONE)
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
var isWeatherShown = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = IntentHelper.getPendingIntent(
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) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
val alarmIntent = IntentHelper.getPendingIntent(
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 = IntentHelper.getPendingIntent(
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 = IntentHelper.getPendingIntent(
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 = IntentHelper.getPendingIntent(
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 = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
pIntentDetail
)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
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.sub_line_rect,
weatherPIntent
)
showSomething = true
isWeatherShown = true
break@loop
}
}
}
}
if (showSomething) {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, if (isWeatherShown) View.GONE else 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)
}
} else {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
// 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, width = bindingView.subLine.width, height = bindingView.subLine.height)
)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
// Generates the widget bitmap from the view
private fun generateWidgetView(typeface: Typeface? = null): LeftAlignedWidgetBinding? {
try {
var isWeatherShownAsGlanceProvider = false
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)
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.showNextEventOnMultipleLines) {
bindingView.nextEvent.apply {
isSingleLine = false
maxLines = 3
gravity = if (rightAligned) Gravity.END else Gravity.START
}
}
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
val diffTime = 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.text = diffTime
if (!Preferences.showNextEventOnMultipleLines) {
bindingView.nextEventDifferenceTime.isVisible = true
} else {
bindingView.nextEvent.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime)
bindingView.nextEventDifferenceTime.isVisible = false
}
} 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 start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateHelper.getDateText(context, start)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateHelper.getDateText(context, now)
} else {
DateHelper.getDateText(context, start)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = Preferences.showWeather && Preferences.weatherIcon != ""
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) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = nextAlarm
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
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.subLineText.text = String.format(
Locale.getDefault(),
"%d°%s %s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit,
WeatherHelper.getWeatherLabel(context, Preferences.weatherIcon)
)
bindingView.subLineIcon.isVisible = true
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.subLineIcon.isVisible = false
} else {
bindingView.subLineIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.subLineIcon.isVisible = true
}
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
showSomething = true
isWeatherShownAsGlanceProvider = 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 (!isWeatherShownAsGlanceProvider) {
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? = androidx.core.content.res.ResourcesCompat.getFont(
context,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
}
)
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()))
@ -37,7 +39,7 @@ class ClockWidget(val context: Context) {
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 5 * 2
)
val clockPIntent = PendingIntent.getActivity(
val clockPIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getClockIntent(context),

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,20 @@
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 java.lang.Exception
import kotlin.math.min
import kotlin.math.roundToInt
class MainWidget : AppWidgetProvider() {
@ -98,17 +71,30 @@ 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)
try {
if (views != null) appWidgetManager.updateAppWidget(appWidgetId, views)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
fun getWidgetView(context: Context, typeface: Typeface?): ViewBinding {
fun getWidgetView(context: Context, width: Int, typeface: Typeface?): RemoteViews? {
return when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidgetView(typeface)
else -> StandardWidget(context).generateWidgetView(typeface)
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(
0,
width,
typeface
)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(
context,
rightAligned = true
).generateWidget(0, width, typeface)
else -> StandardWidget(context).generateWidget(0, width, typeface)
}
}
}

View File

@ -24,18 +24,20 @@ import android.util.TypedValue
import android.view.animation.AlphaAnimation
import android.widget.RelativeLayout
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatDelegate
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.core.animation.addListener
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.OnSingleClickListener
import com.tommasoberlose.anotherwidget.global.Preferences
import java.util.*
fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name)
fun Context.toast(message: String, long: Boolean = false) {
val toast = Toast.makeText(this, message, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)
val toast = Toast.makeText(applicationContext, message, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)
// toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
@ -192,7 +194,7 @@ fun String.isValidEmail(): Boolean
Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun Context.isDarkTheme(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return Preferences.darkThemePreference == AppCompatDelegate.MODE_NIGHT_YES || Preferences.darkThemePreference == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
fun Activity.isNotificationAccessGranted(): Boolean = Settings.Secure.getString(this.contentResolver,"enabled_notification_listeners").contains(this.packageName)
@ -265,3 +267,11 @@ fun View.setOnSingleClickListener(l: View.OnClickListener) {
fun View.setOnSingleClickListener(l: (View) -> Unit) {
setOnClickListener(OnSingleClickListener(l))
}
fun ignoreExceptions(function: () -> Unit) = run {
try {
function.invoke()
} catch (ex: Exception) {
ex.printStackTrace()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

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: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Some files were not shown because too many files have changed in this diff Show More