Compare commits

...

96 Commits

Author SHA1 Message Date
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
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
f325af26f8 Bump version to v2.3.1 2021-05-05 14:54:24 +02:00
b61fbd193c Added single multiple clock 2021-05-05 14:53:43 +02:00
03d9446369 Bump version to v2.3.0 2021-05-05 12:44:21 +02:00
23f94e63c6 Merge branch 'main' of github.com:tommasoberlose/another-widget into develop 2021-05-05 12:13:54 +02:00
01775d3a3a Merge pull request #308 from tommasoberlose/widget-align
Widget align and bugfixes
2021-05-05 12:12:55 +02:00
67abd14bb1 Bugfixes 2021-05-05 12:11:51 +02:00
854cfac28c Bugfixes 2021-05-05 10:54:09 +02:00
8fafe591e8 Merge pull request #299 from cccClyde/main
Updated localization for Simplified Chinese.
2021-05-05 10:35:20 +02:00
a85fefe4dc Merge pull request #307 from tommasoberlose/revert-305-patch-2
Revert "Update strings for ru.xml"
2021-05-05 10:33:40 +02:00
974185a89b Revert "Update strings for ru.xml" 2021-05-05 10:33:19 +02:00
bbe1497f8b Merge pull request #305 from Dima-a-galaxy/patch-2
Update strings for ru.xml
2021-05-05 10:32:56 +02:00
1f22426dec Update align support 2021-05-04 18:32:20 +02:00
40644f3657 update widget 2021-05-03 23:01:54 +02:00
a01c8eff63 Update strings.xml 2021-04-21 10:09:43 +04:00
1ee25bcc89 Updated localization for Simplified Chinese.
Updated localization for Simplified Chinese, now it's 100% completed.
2021-04-02 13:29:18 +08:00
1bd18ac486 Merge branch 'main' of github.com:tommasoberlose/another-widget into develop 2021-01-24 13:00:24 +01:00
d24ac198a4 Merge pull request #283 from Lambada10/main
Add Polish Translation
2021-01-24 13:00:07 +01:00
81578f5f4a Fix #282, bug fixes 2021-01-24 12:48:34 +01:00
8234a87a2a Translations 2021-01-18 14:17:27 +01:00
0774c6bdbe Translations 2021-01-18 12:12:58 +01:00
57eecd630d Fix #275 2021-01-14 17:41:33 +01:00
cce86a970c Fix #278 2021-01-14 14:14:00 +01:00
6ea40a51fe Beta version 2.2.2-beta9 2021-01-12 15:46:48 +01:00
1721dff3cf Fix #273, fix #271 2021-01-12 15:42:16 +01:00
c389d50b09 Fix background service error 2021-01-12 09:51:00 +01:00
0ea55db4b1 Update strings 2021-01-11 15:10:28 +01:00
3fba50fd2c Fix #265 2021-01-11 09:49:00 +01:00
06583197c7 Fix widget refresh intent 2021-01-11 09:42:55 +01:00
5176331e84 Version 2.2.2-beta5 2021-01-11 01:34:38 +01:00
65f83caeb5 Merge pull request #267 from Moutony/patch-27
French v2.2.2 (120)
2021-01-11 01:33:22 +01:00
60e8c267bf Version 2.2.2-beta4 2021-01-11 01:32:29 +01:00
10a3204808 Fix #263 2021-01-11 01:30:31 +01:00
98c509ef27 French v2.2.2 (120) 2021-01-10 19:06:20 +01:00
ab1df499af beta version 2.2.2-beta3 2021-01-10 18:02:44 +01:00
ed9a4042c8 Fix #259 2021-01-10 17:56:48 +01:00
a102776214 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2021-01-10 17:21:34 +01:00
0778ad4df5 Merge pull request #264 from Drumber/translation
Update German strings.xml
2021-01-10 17:21:27 +01:00
8dce8a74b3 Fix #242 2021-01-10 17:20:52 +01:00
24bb811f93 Fix update calendar service 2021-01-10 15:54:54 +01:00
0500e8d8e8 Update strings.xml
Added missing translations
2021-01-10 14:16:19 +01:00
d2087d094f Beta version 2.2.2-beta1 2021-01-10 12:52:20 +01:00
34fb35f2ab Fix #262 2021-01-10 12:48:17 +01:00
108ecdece0 Update transitions 2021-01-10 02:08:15 +01:00
e3f4995e93 Fix icon selector 2021-01-10 00:41:36 +01:00
0cdc5a3c6d Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2021-01-09 20:28:03 +01:00
9f039eec3c Merge pull request #258 from Moutony/patch-26
Update strings.xml
2021-01-09 20:27:51 +01:00
e9effbe799 Update animations 2021-01-09 20:26:23 +01:00
889783bb4e update ui 2021-01-09 18:33:22 +01:00
d32f680519 Update UI 2021-01-09 00:24:05 +01:00
c1d14f93bf Improve performances 2021-01-08 17:49:12 +01:00
b903fff10f UI update 2021-01-08 01:17:48 +01:00
20c5ce61b4 Update ui 2021-01-07 22:51:45 +01:00
5bb81772f4 UI update 2021-01-07 18:24:20 +01:00
526a9ac6ac Change UI 2021-01-07 16:11:10 +01:00
98db1380b7 Move to kotlin data binding and update main fragoment 2021-01-07 15:09:58 +01:00
ce9b343e0e Change file names 2021-01-07 12:44:37 +01:00
21ec3c3f5d Update strings.xml 2021-01-06 22:06:43 +01:00
88950a84b4 Update strings.xml 2021-01-06 21:58:01 +01:00
fb853975e0 Update app navigation 2021-01-06 21:31:01 +01:00
329eee6339 Add new dark icons 2021-01-06 21:11:10 +01:00
c595168320 Merge develop 2021-01-06 17:33:24 +01:00
61e3e43fd7 Publish v2.1.1 2021-01-06 17:30:42 +01:00
1513b96313 Bugfixes, fix #257 2021-01-06 17:26:32 +01:00
1cc5558d9e Add dark icons 2021-01-06 17:19:36 +01:00
e12e908496 UI update 2021-01-05 17:53:00 +01:00
2aed9e3b25 Merge branch 'develop' of github.com:tommasoberlose/another-widget into design-update 2021-01-05 11:08:26 +01:00
536ed64d41 Default temp unit and weather provider based on locale 2021-01-05 11:08:13 +01:00
6b6ec633ee Update main design 2021-01-05 10:46:12 +01:00
5b2d245e80 Merge develop 2021-01-04 12:07:59 +01:00
0aec34dcd2 Bugfixes 2021-01-04 12:04:25 +01:00
b2a9b9fbb3 Fix #255 2021-01-03 17:51:31 +01:00
768f04825e Building 2021-01-03 17:25:00 +01:00
80ee877bf2 Merge pull request #252 from devindang/master
Update new strings for Chinese Simplified
2021-01-03 17:24:18 +01:00
ac7839ecdc Merge pull request #244 from Moutony/patch-25
Update French Greetings
2021-01-03 17:24:11 +01:00
66d0214cd9 Move the location updates to a foreground service and fix multiple bugs 2021-01-03 17:23:22 +01:00
e069b8f6ab greetings 2020-11-27 23:47:13 +08:00
8a681f0cd7 Update New Strings 2020-11-25 20:46:02 +08:00
92158ec5f2 Update strings.xml 2020-11-22 12:21:56 +01:00
ebb37f21ed Remove useless icons 2020-10-30 19:26:01 +01:00
2a389cb422 Update strings.xml 2020-10-29 12:12:27 +01:00
4504a0617e Update Fitness in Glance 2020-10-28 14:58:22 +01:00
9ed34ee17e Update French Greetings
Thanks for adding antislash before apostrophes. I always forget it haha
But, in French typography, there is a (narrow) no-break space U+202F before these punctuations ! ? ; :
2020-10-28 14:47:11 +01:00
a7294edfd4 Fix notification listener 2020-10-27 18:28:21 +01:00
3073 changed files with 13349 additions and 8354 deletions

5
.gitignore vendored
View File

@ -8,4 +8,7 @@
.externalNativeBuild .externalNativeBuild
/tasksintegration/build /tasksintegration/build
/app/google-services.json /app/google-services.json
apikey.properties apikey.properties
./.idea/*
app/release/*
/app/release/*

View File

@ -19,6 +19,29 @@
<option name="children"> <option name="children">
<map> <map>
<entry key="clipArt"> <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> <value>
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
@ -45,14 +68,47 @@
<PersistentState> <PersistentState>
<option name="children"> <option name="children">
<map> <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"> <entry key="foregroundImage">
<value> <value>
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
<map> <map>
<entry key="color" value="000000" /> <entry key="color" value="000000" />
<entry key="imagePath" value="$USER_HOME$/Desktop/logo-white.png" />
<entry key="scalingPercent" value="70" /> <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> </map>
</option> </option>
</PersistentState> </PersistentState>
@ -64,7 +120,6 @@
<map> <map>
<entry key="backgroundAssetType" value="COLOR" /> <entry key="backgroundAssetType" value="COLOR" />
<entry key="backgroundColor" value="ffffff" /> <entry key="backgroundColor" value="ffffff" />
<entry key="foregroundImage" value="$USER_HOME$/Desktop/Artboard Copy 3.png" />
<entry key="legacyIconShape" value="CIRCLE" /> <entry key="legacyIconShape" value="CIRCLE" />
</map> </map>
</option> </option>
@ -77,6 +132,29 @@
<option name="children"> <option name="children">
<map> <map>
<entry key="clipArt"> <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> <value>
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
@ -98,6 +176,104 @@
<option name="children"> <option name="children">
<map> <map>
<entry key="clipArt"> <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> <value>
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
@ -123,6 +299,29 @@
</PersistentState> </PersistentState>
</value> </value>
</entry> </entry>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="round_aspect_ratio_24" />
<entry key="sourceFile" value="$USER_HOME$/Desktop/round_aspect_ratio_24.svg" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map> </map>
</option> </option>
</component> </component>

Binary file not shown.

View File

@ -17,7 +17,6 @@
<package name="" alias="true" withSubpackages="true" /> <package name="" alias="true" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">

2
.idea/misc.xml generated
View File

@ -61,7 +61,7 @@
</profile-state> </profile-state>
</entry> </entry>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

140
.idea/navEditor.xml generated
View File

@ -46,6 +46,146 @@
</LayoutPositions> </LayoutPositions>
</value> </value>
</entry> </entry>
<entry key="settings_nav_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="calendarTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="190" />
<option name="y" value="4" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="clockTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="494" />
<option name="y" value="302" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="generalTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-48" />
<option name="y" value="133" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="gesturesFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="703" />
<option name="y" value="14" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="glanceTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="2" />
<option name="y" value="-198" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="tabSelectorFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-537" />
<option name="y" value="216" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_tabSelectorFragment_to_calendarTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_tabSelectorFragment_to_clockTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_tabSelectorFragment_to_generalTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_tabSelectorFragment_to_glanceTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_tabSelectorFragment_to_typographyTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_tabSelectorFragment_to_weatherTabFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="typographyTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="470" />
<option name="y" value="-207" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="weatherTabFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-301" />
<option name="y" value="-160" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map> </map>
</option> </option>
</component> </component>

View File

@ -3,6 +3,7 @@
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <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.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@ -6,7 +6,6 @@ apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android' apply plugin: 'realm-android'
@ -16,15 +15,15 @@ apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion "29.0.3" buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
applicationId "com.tommasoberlose.anotherwidget" applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 30
versionCode 110 versionCode 138
versionName "2.0.15" versionName "2.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY']) buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
@ -55,68 +54,69 @@ android {
packagingOptions { packagingOptions {
exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/DEPENDENCIES'
} }
dataBinding {
enabled = true
}
viewBinding.enabled = true buildFeatures {
dataBinding = true
viewBinding = true
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI // UI
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.3.0'
implementation 'net.idik:slimadapter:2.1.2' implementation 'net.idik:slimadapter:2.1.2'
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
implementation 'com.kyleduo.switchbutton:library:2.0.3'
// Lifecycle // Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.work:work-runtime-ktx:2.4.0" implementation "androidx.work:work-runtime-ktx:2.5.0"
// EventBus // EventBus
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
// Navigation // Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// Other // Other
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'joda-time:joda-time:2.10.6' implementation 'net.danlew:android.joda:2.10.9'
implementation 'me.everything:providers-android:1.0.1' implementation 'me.everything:providers-android:1.0.1'
implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2' implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
//Glide //Glide
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0'
// Fitness // Fitness
implementation 'com.google.android.gms:play-services-fitness:18.0.0' implementation 'com.google.android.gms:play-services-fitness:20.0.0'
implementation 'com.google.android.gms:play-services-auth:18.1.0' implementation 'com.google.android.gms:play-services-auth:19.0.0'
//Weather //Weather
implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2' implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2'
implementation 'com.google.android.gms:play-services-location:17.1.0' implementation 'com.google.android.gms:play-services-location:18.0.0'
// Billing // Billing
implementation 'com.android.billingclient:billing:3.0.1' implementation 'com.android.billingclient:billing:3.0.3'
implementation 'com.android.billingclient:billing-ktx:3.0.1' implementation 'com.android.billingclient:billing-ktx:3.0.3'
// KTX // KTX
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.palette:palette-ktx:1.0.0" implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
@ -128,14 +128,14 @@ dependencies {
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1" implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1"
//Coroutines //Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Add the Firebase SDK for Crashlytics. // Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.2.2' implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
// Preferences // Preferences
implementation 'com.chibatching.kotpref:kotpref:2.11.0' implementation 'com.chibatching.kotpref:kotpref:2.13.1'
implementation 'com.chibatching.kotpref:livedata-support:2.10.0' implementation 'com.chibatching.kotpref:livedata-support:2.13.1'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
// Permissions // Permissions

Binary file not shown.

View File

@ -5,7 +5,6 @@
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
@ -13,7 +12,7 @@
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/> <uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -32,16 +31,17 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.activities.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.settings.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.settings.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.AppNotificationsFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget"> <receiver android:name=".ui.widgets.MainWidget">
<intent-filter> <intent-filter>
@ -151,9 +151,21 @@
</receiver> </receiver>
<service <service
android:name=".services.UpdateCalendarJob" android:name=".services.UpdateCalendarService"
android:permission="android.permission.BIND_JOB_SERVICE" android:enabled="true"
android:exported="true"/> android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application> </application>
<queries>
<package android:name="com.google.android.apps.fitness"/>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
</manifest> </manifest>

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

@ -1,13 +1,16 @@
package com.tommasoberlose.anotherwidget package com.tommasoberlose.anotherwidget
import android.Manifest
import android.app.Application import android.app.Application
import android.util.Log import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.chibatching.kotpref.Kotpref import com.chibatching.kotpref.Kotpref
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import net.danlew.android.joda.JodaTimeAndroid
class AWApplication : Application() { class AWApplication : Application() {
override fun onCreate() { override fun onCreate() {

View File

@ -3,40 +3,30 @@ package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.text.Editable import android.view.LayoutInflater
import android.text.TextWatcher
import android.util.Log
import android.view.View import android.view.View
import android.widget.GridLayout import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.SeekBar
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.helpers.ColorHelper 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.isColorDark
import com.tommasoberlose.anotherwidget.utils.expand import com.tommasoberlose.anotherwidget.helpers.ColorHelper.pasteFromClipboard
import com.tommasoberlose.anotherwidget.utils.reveal import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.utils.toPixel import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.warkiz.widget.IndicatorSeekBar import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams import com.warkiz.widget.SeekParams
import kotlinx.android.synthetic.main.bottom_sheet_menu_hor.*
import kotlinx.android.synthetic.main.bottom_sheet_menu_hor.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu_hor.view.color_loader
import kotlinx.android.synthetic.main.color_picker_menu_item.view.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.util.prefs.Preferences
class BottomSheetColorPicker( class BottomSheetColorPicker(
context: Context, context: Context,
@ -46,30 +36,61 @@ class BottomSheetColorPicker(
private val onColorSelected: ((selectedValue: Int) -> Unit)? = null, private val onColorSelected: ((selectedValue: Int) -> Unit)? = null,
private val showAlphaSelector: Boolean = false, private val showAlphaSelector: Boolean = false,
private val alpha: Int = 0, 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) { ) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var loadingJobs: ArrayList<Job> = ArrayList() private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter 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() { override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_menu_hor, null)
window?.setDimAmount(0f) window?.setDimAmount(0f)
// Header // Header
view.header.isVisible = header != null binding.header.isVisible = header != null
view.header_text.text = header ?: "" 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 // Alpha
view.alpha_selector_container.isVisible = showAlphaSelector binding.alphaSelectorContainer.isVisible = showAlphaSelector
view.alpha_selector.setProgress(alpha.toFloat()) binding.alphaSelector.setProgress(alpha.toFloat())
view.text_alpha.text = "%s: %s%%".format(context.getString(R.string.alpha), alpha) binding.textAlpha.text = "%s: %s%%".format(context.getString(R.string.alpha), alpha)
view.alpha_selector.onSeekChangeListener = object : OnSeekChangeListener { binding.alphaSelector.onSeekChangeListener = object : OnSeekChangeListener {
override fun onSeeking(seekParams: SeekParams?) { override fun onSeeking(seekParams: SeekParams?) {
seekParams?.let { seekParams?.let {
view.text_alpha.text = "%s: %s%%".format(context.getString(R.string.alpha), it.progress) binding.textAlpha.text =
onAlphaChangeListener?.invoke(it.progress) "%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?) { override fun onStartTrackingTouch(seekBar: IndicatorSeekBar?) {
@ -79,20 +100,19 @@ class BottomSheetColorPicker(
} }
// List // List
adapter = SlimAdapter.create() adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) { loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
val listView = View.inflate(context, R.layout.bottom_sheet_menu_list, null) as RecyclerView listBinding.root.setHasFixedSize(true)
listView.setHasFixedSize(true)
val mLayoutManager = GridLayoutManager(context, 6) val mLayoutManager = GridLayoutManager(context, 6)
listView.layoutManager = mLayoutManager listBinding.root.layoutManager = mLayoutManager
adapter adapter
.register<Int>(R.layout.color_picker_menu_item) { item, injector -> .register<Int>(R.layout.color_picker_menu_item) { item, injector ->
injector injector
.with<MaterialCardView>(R.id.color) { .with<MaterialCardView>(R.id.color) {
it.setCardBackgroundColor(ColorStateList.valueOf(item)) it.setCardBackgroundColor(ColorStateList.valueOf(item))
it.strokeWidth = if ((colors.indexOf(item) == 0 && !context.isDarkTheme()) || (colors.indexOf(item) == 10 && context.isDarkTheme())) 2 else 0
} }
.with<AppCompatImageView>(R.id.check) { .with<AppCompatImageView>(R.id.check) {
if (getSelected?.invoke() == item) { if (getSelected?.invoke() == item) {
@ -113,22 +133,25 @@ class BottomSheetColorPicker(
onColorSelected?.invoke(item) onColorSelected?.invoke(item)
val position = adapter.data.indexOf(item) val position = adapter.data.indexOf(item)
adapter.notifyItemChanged(position) adapter.notifyItemChanged(position)
(listView.layoutManager as GridLayoutManager).scrollToPositionWithOffset(position,0) (listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(position,0)
} }
} }
.attachTo(listView) .attachTo(listBinding.root)
adapter.updateData(colors.toList()) adapter.updateData(colors.toList())
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
view.color_loader.isVisible = false binding.loader.isVisible = false
view.list_container.addView(listView) binding.listContainer.addView(listBinding.root)
this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
view.list_container.isVisible = true binding.listContainer.isVisible = true
val idx = colors.toList().indexOf(getSelected?.invoke())
(listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(idx,0)
} }
}) })
setContentView(view) setContentView(binding.root)
super.show() super.show()
} }

View File

@ -1,16 +1,17 @@
package com.tommasoberlose.anotherwidget.components package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.* import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuBinding
import kotlinx.android.synthetic.main.bottom_sheet_menu_item.view.* import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuItemBinding
import org.w3c.dom.Text
/** /**
* [BottomSheetDialogFragment] that uses a custom * [BottomSheetDialogFragment] that uses a custom
@ -24,6 +25,8 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
private var callback: ((selectedValue: T) -> Unit)? = null private var callback: ((selectedValue: T) -> Unit)? = null
private var multipleSelectionCallback: ((selectedValues: ArrayList<T>) -> Unit)? = null private var multipleSelectionCallback: ((selectedValues: ArrayList<T>) -> Unit)? = null
private var binding = BottomSheetMenuBinding.inflate(LayoutInflater.from(context))
fun setSelectedValue(res: T): BottomSheetMenu<T> { fun setSelectedValue(res: T): BottomSheetMenu<T> {
selectedRes = ArrayList(listOf(res)) selectedRes = ArrayList(listOf(res))
return this return this
@ -50,33 +53,31 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
} }
override fun show() { override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_menu, null)
// Header // Header
view.header.isVisible = header != null binding.header.isVisible = header != null
view.header_text.text = header ?: "" binding.headerText.text = header ?: ""
view.warning_text.isVisible = message != null binding.warningText.isVisible = message != null
view.warning_text.text = message ?: "" binding.warningText.text = message ?: ""
view.warning_text.setTextColor(ContextCompat.getColor(context, if (isMessageWarning) R.color.warningColorText else R.color.colorSecondaryText)) binding.warningText.setTextColor(ContextCompat.getColor(context, if (isMessageWarning) R.color.warningColorText else R.color.colorSecondaryText))
// Menu // Menu
for (item in items) { for (item in items) {
val itemBinding = BottomSheetMenuItemBinding.inflate(LayoutInflater.from(context))
if (item.value != null) { if (item.value != null) {
val itemView = View.inflate(context, R.layout.bottom_sheet_menu_item, null) itemBinding.label.text = item.title
itemView.label.text = item.title
if (isMultiSelection) { if (isMultiSelection) {
itemView.icon_check.isVisible = selectedRes.contains(item.value) itemBinding.iconCheck.isVisible = selectedRes.contains(item.value)
itemView.label.setTextColor( itemBinding.label.setTextColor(
if (selectedRes.contains(item.value)) ContextCompat.getColor( if (selectedRes.contains(item.value)) ContextCompat.getColor(
context, context,
R.color.colorPrimaryText R.color.colorPrimaryText
) else ContextCompat.getColor(context, R.color.colorSecondaryText) ) else ContextCompat.getColor(context, R.color.colorSecondaryText)
) )
} else { } else {
itemView.isSelected = selectedRes.contains(item.value) itemBinding.root.isSelected = selectedRes.contains(item.value)
} }
itemView.setOnClickListener { itemBinding.root.setOnClickListener {
if (!isMultiSelection) { if (!isMultiSelection) {
callback?.invoke(item.value) callback?.invoke(item.value)
this.dismiss() this.dismiss()
@ -88,8 +89,8 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
} }
multipleSelectionCallback?.invoke(selectedRes) multipleSelectionCallback?.invoke(selectedRes)
itemView.icon_check.isVisible = selectedRes.contains(item.value) itemBinding.iconCheck.isVisible = selectedRes.contains(item.value)
itemView.label.setTextColor( itemBinding.label.setTextColor(
if (selectedRes.contains(item.value)) ContextCompat.getColor( if (selectedRes.contains(item.value)) ContextCompat.getColor(
context, context,
R.color.colorPrimaryText R.color.colorPrimaryText
@ -97,17 +98,16 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
) )
} }
item.renderCallback?.invoke(itemView.label) item.renderCallback?.invoke(itemBinding.label)
} }
view.menu.addView(itemView) binding.menu.addView(itemBinding.root)
} else { } else {
val itemView = View.inflate(context, R.layout.bottom_sheet_menu_divider, null) itemBinding.label.text = item.title
itemView.label.text = item.title binding.menu.addView(itemBinding.root)
view.menu.addView(itemView)
} }
} }
setContentView(view) setContentView(binding.root)
super.show() super.show()
} }

View File

@ -0,0 +1,108 @@
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)
this@BottomSheetPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
binding.listContainer.isVisible = true
val idx = items.toList().indexOfFirst { it.value == getSelected?.invoke() }
(listBinding.root.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(idx,0)
}
})
setContentView(binding.root)
super.show()
}
override fun onStop() {
loadingJobs.forEach { it.cancel() }
super.onStop()
}
class MenuItem<T>(val title: String, val value: T? = null)
}

View File

@ -5,32 +5,33 @@ import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.WeatherProviderSettingsLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.utils.openURI import com.tommasoberlose.anotherwidget.utils.openURI
import kotlinx.android.synthetic.main.weather_provider_settings_layout.view.*
class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) { class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var binding: WeatherProviderSettingsLayoutBinding = WeatherProviderSettingsLayoutBinding.inflate(android.view.LayoutInflater.from(context))
init { init {
val view = View.inflate(context, R.layout.weather_provider_settings_layout, null) binding.apiKeyContainer.isVisible = WeatherHelper.isKeyRequired()
view.api_key_container.isVisible = WeatherHelper.isKeyRequired() binding.actionSaveKey.isVisible = WeatherHelper.isKeyRequired()
view.action_save_key.isVisible = WeatherHelper.isKeyRequired()
WeatherHelper.getProviderInfoTitle(context).let { title -> WeatherHelper.getProviderInfoTitle(context).let { title ->
view.info_title.text = title binding.infoTitle.text = title
view.info_title.isVisible = title != "" binding.infoTitle.isVisible = title != ""
} }
WeatherHelper.getProviderInfoSubtitle(context).let { subtitle -> WeatherHelper.getProviderInfoSubtitle(context).let { subtitle ->
view.info_subtitle.text = subtitle binding.infoSubtitle.text = subtitle
view.info_subtitle.isVisible = subtitle != "" binding.infoSubtitle.isVisible = subtitle != ""
} }
view.info_provider.text = WeatherHelper.getProviderName(context) binding.infoProvider.text = WeatherHelper.getProviderName(context)
view.api_key.editText?.setText(when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) { binding.apiKey.editText?.setText(when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi
@ -41,12 +42,12 @@ class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit)
null -> "" null -> ""
}) })
view.action_open_provider.setOnClickListener { binding.actionOpenProvider.setOnClickListener {
context.openURI(WeatherHelper.getProviderLink()) context.openURI(WeatherHelper.getProviderLink())
} }
view.action_save_key.setOnClickListener { binding.actionSaveKey.setOnClickListener {
val key = view.api_key.editText?.text.toString() val key = binding.apiKey.editText?.text.toString()
when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) { when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen = key Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen = key
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit = key Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit = key
@ -59,6 +60,6 @@ class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit)
dismiss() dismiss()
} }
setContentView(view) setContentView(binding.root)
} }
} }

View File

@ -1,28 +1,30 @@
package com.tommasoberlose.anotherwidget.components package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.CustomNotesDialogLayoutBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import kotlinx.android.synthetic.main.custom_notes_dialog_layout.view.*
class CustomNotesDialog(context: Context, callback: (() -> Unit)?) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) { class CustomNotesDialog(context: Context, callback: (() -> Unit)?) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
init { private var binding: CustomNotesDialogLayoutBinding = CustomNotesDialogLayoutBinding.inflate(LayoutInflater.from(context))
val view = View.inflate(context, R.layout.custom_notes_dialog_layout, null)
view.notes.setText(Preferences.customNotes)
view.action_positive.setOnClickListener { init {
Preferences.customNotes = view.notes.text.toString() binding.notes.setText(Preferences.customNotes)
binding.actionPositive.setOnClickListener {
Preferences.customNotes = binding.notes.text.toString()
this.dismiss() this.dismiss()
callback?.invoke() callback?.invoke()
} }
view.notes.requestFocus() binding.notes.requestFocus()
setContentView(view) setContentView(binding.root)
} }
} }

View File

@ -7,10 +7,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.EventLog import android.view.LayoutInflater
import android.util.Log
import android.view.View
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInAccount
@ -22,6 +19,7 @@ import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.GlanceProviderSettingsLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
@ -29,21 +27,22 @@ import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GreetingsHelper import com.tommasoberlose.anotherwidget.helpers.GreetingsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.ui.activities.AppNotificationsFilterActivity import com.tommasoberlose.anotherwidget.ui.activities.tabs.AppNotificationsFilterActivity
import com.tommasoberlose.anotherwidget.ui.activities.MusicPlayersFilterActivity import com.tommasoberlose.anotherwidget.ui.activities.tabs.MediaInfoFormatActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.MusicPlayersFilterActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.android.synthetic.main.glance_provider_settings_layout.view.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
class GlanceSettingsDialog(val context: Activity, val provider: Constants.GlanceProviderId, private val statusCallback: (() -> Unit)?) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) { class GlanceSettingsDialog(val context: Activity, val provider: Constants.GlanceProviderId, private val statusCallback: (() -> Unit)?) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var binding: GlanceProviderSettingsLayoutBinding = GlanceProviderSettingsLayoutBinding.inflate(LayoutInflater.from(context))
override fun show() { override fun show() {
val view = View.inflate(context, R.layout.glance_provider_settings_layout, null)
/* TITLE */ /* TITLE */
view.title.text = when (provider) { binding.title.text = when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> context.getString(R.string.settings_show_music_title) Constants.GlanceProviderId.PLAYING_SONG -> context.getString(R.string.settings_show_music_title)
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> context.getString(R.string.settings_show_next_alarm_title) Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> context.getString(R.string.settings_show_next_alarm_title)
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> context.getString(R.string.settings_low_battery_level_title) Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> context.getString(R.string.settings_low_battery_level_title)
@ -55,7 +54,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
/* SUBTITLE*/ /* SUBTITLE*/
view.subtitle.text = when (provider) { binding.subtitle.text = when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> context.getString(R.string.settings_show_music_subtitle) Constants.GlanceProviderId.PLAYING_SONG -> context.getString(R.string.settings_show_music_subtitle)
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> context.getString(R.string.settings_show_next_alarm_subtitle) Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> context.getString(R.string.settings_show_next_alarm_subtitle)
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> context.getString(R.string.settings_low_battery_level_subtitle) Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> context.getString(R.string.settings_low_battery_level_subtitle)
@ -67,53 +66,58 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
/* SONG */ /* SONG */
view.action_filter_music_players.isVisible = provider == Constants.GlanceProviderId.PLAYING_SONG binding.actionFilterMusicPlayers.isVisible = provider == Constants.GlanceProviderId.PLAYING_SONG
binding.actionChangeMediaInfoFormat.isVisible = provider == Constants.GlanceProviderId.PLAYING_SONG
if (provider == Constants.GlanceProviderId.PLAYING_SONG) { if (provider == Constants.GlanceProviderId.PLAYING_SONG) {
view.action_filter_music_players.setOnClickListener { binding.actionFilterMusicPlayers.setOnClickListener {
dismiss() dismiss()
context.startActivityForResult(Intent(context, MusicPlayersFilterActivity::class.java), 0) context.startActivityForResult(Intent(context, MusicPlayersFilterActivity::class.java), 0)
} }
checkNotificationPermission(view) binding.actionChangeMediaInfoFormat.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, MediaInfoFormatActivity::class.java), 0)
}
checkNotificationPermission()
} }
/* ALARM */ /* ALARM */
view.alarm_set_by_container.isVisible = provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM binding.alarmSetByContainer.isVisible = provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM
if (provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM) { if (provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM) {
view.header.text = context.getString(R.string.information_header) binding.header.text = context.getString(R.string.information_header)
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
checkNextAlarm(view) checkNextAlarm()
} }
/* GOOGLE STEPS */ /* GOOGLE STEPS */
view.action_toggle_google_fit.isVisible = provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS binding.actionToggleGoogleFit.isVisible = provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS
if (provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS) { if (provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS) {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
checkFitnessPermission(view) checkFitnessPermission()
checkGoogleFitConnection(view) checkGoogleFitConnection()
} }
/* BATTERY INFO */ /* BATTERY INFO */
if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) { if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
view.header.isVisible = false binding.header.isVisible = false
view.divider.isVisible = false binding.divider.isVisible = false
} }
/* NOTIFICATIONS */ /* NOTIFICATIONS */
view.action_filter_notifications_app.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS binding.actionFilterNotificationsApp.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
view.action_change_notification_timer.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS binding.actionChangeNotificationTimer.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
if (provider == Constants.GlanceProviderId.NOTIFICATIONS) { if (provider == Constants.GlanceProviderId.NOTIFICATIONS) {
checkLastNotificationsPermission(view) checkLastNotificationsPermission()
val stringArray = context.resources.getStringArray(R.array.glance_notifications_timeout) val stringArray = context.resources.getStringArray(R.array.glance_notifications_timeout)
view.action_filter_notifications_app.setOnClickListener { binding.actionFilterNotificationsApp.setOnClickListener {
dismiss() dismiss()
context.startActivityForResult(Intent(context, AppNotificationsFilterActivity::class.java), 0) context.startActivityForResult(Intent(context, AppNotificationsFilterActivity::class.java), 0)
} }
view.notification_timer_label.text = stringArray[Preferences.hideNotificationAfter] binding.notificationTimerLabel.text = stringArray[Preferences.hideNotificationAfter]
view.action_change_notification_timer.setOnClickListener { binding.actionChangeNotificationTimer.setOnClickListener {
val dialog = BottomSheetMenu<Int>(context, header = context.getString(R.string.glance_notification_hide_timeout_title)).setSelectedValue(Preferences.hideNotificationAfter) val dialog = BottomSheetMenu<Int>(context, header = context.getString(R.string.glance_notification_hide_timeout_title)).setSelectedValue(Preferences.hideNotificationAfter)
Constants.GlanceNotificationTimer.values().forEachIndexed { index, timeout -> Constants.GlanceNotificationTimer.values().forEachIndexed { index, timeout ->
dialog.addItem(stringArray[index], timeout.value) dialog.addItem(stringArray[index], timeout.rawValue)
} }
dialog.addOnSelectItemListener { value -> dialog.addOnSelectItemListener { value ->
Preferences.hideNotificationAfter = value Preferences.hideNotificationAfter = value
@ -124,20 +128,20 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
/* GREETINGS */ /* GREETINGS */
if (provider == Constants.GlanceProviderId.GREETINGS) { if (provider == Constants.GlanceProviderId.GREETINGS) {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
view.header.isVisible = false binding.header.isVisible = false
view.divider.isVisible = false binding.divider.isVisible = false
} }
/* EVENTS */ /* EVENTS */
if (provider == Constants.GlanceProviderId.EVENTS) { if (provider == Constants.GlanceProviderId.EVENTS) {
view.header.isVisible = false binding.header.isVisible = false
view.divider.isVisible = false binding.divider.isVisible = false
checkCalendarConfig(view) checkCalendarConfig()
} }
/* TOGGLE */ /* TOGGLE */
view.provider_switch.isChecked = when (provider) { binding.providerSwitch.setCheckedImmediatelyNoEvent(when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> Preferences.showNextAlarm Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> Preferences.showNextAlarm
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> Preferences.showBatteryCharging Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> Preferences.showBatteryCharging
@ -146,11 +150,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications
Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings
Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider
} })
var job: Job? = null var job: Job? = null
view.provider_switch.setOnCheckedChangeListener { _, isChecked -> binding.providerSwitch.setOnCheckedChangeListener { _, isChecked ->
job?.cancel() job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) { job = GlobalScope.launch(Dispatchers.IO) {
delay(300) delay(300)
@ -158,18 +162,18 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
when (provider) { when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> { Constants.GlanceProviderId.PLAYING_SONG -> {
Preferences.showMusic = isChecked Preferences.showMusic = isChecked
checkNotificationPermission(view) checkNotificationPermission()
} }
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> { Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked Preferences.showNextAlarm = isChecked
checkNextAlarm(view) checkNextAlarm()
} }
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> { Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked Preferences.showBatteryCharging = isChecked
} }
Constants.GlanceProviderId.NOTIFICATIONS -> { Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked Preferences.showNotifications = isChecked
checkLastNotificationsPermission(view) checkLastNotificationsPermission()
} }
Constants.GlanceProviderId.GREETINGS -> { Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked Preferences.showGreetings = isChecked
@ -197,9 +201,9 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Preferences.showDailySteps = false Preferences.showDailySteps = false
} }
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
checkFitnessPermission(view) checkFitnessPermission()
checkGoogleFitConnection(view) checkGoogleFitConnection()
} }
Constants.GlanceProviderId.EVENTS -> { Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked Preferences.showEventsAsGlanceProvider = isChecked
@ -212,11 +216,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
} }
setContentView(view) setContentView(binding.root)
super.show() super.show()
} }
private fun checkNextAlarm(view: View) { private fun checkNextAlarm() {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) { if (alarm != null && alarm.showIntent != null) {
@ -226,69 +230,71 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} catch (e: Exception) { } catch (e: Exception) {
alarm.showIntent?.creatorPackage ?: "" alarm.showIntent?.creatorPackage ?: ""
} }
view.alarm_set_by_title.text = context.getString(R.string.settings_show_next_alarm_app_title).format(appNameOrPackage) binding.alarmSetByTitle.text = context.getString(R.string.settings_show_next_alarm_app_title).format(appNameOrPackage)
view.alarm_set_by_subtitle.text = if (AlarmHelper.isAlarmProbablyWrong(context)) context.getString(R.string.settings_show_next_alarm_app_subtitle_wrong) else context.getString(R.string.settings_show_next_alarm_app_subtitle_correct) binding.alarmSetBySubtitle.text = if (AlarmHelper.isAlarmProbablyWrong(context)) context.getString(R.string.settings_show_next_alarm_app_subtitle_wrong) else context.getString(R.string.settings_show_next_alarm_app_subtitle_correct)
view.alarm_set_by_title.isVisible = true binding.alarmSetByContainer.isVisible = true
} else { } else {
view.alarm_set_by_title.isVisible = false binding.alarmSetByContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
} }
} }
statusCallback?.invoke() statusCallback?.invoke()
} }
private fun checkCalendarConfig(view: View) { private fun checkCalendarConfig() {
if (!Preferences.showEvents || !context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) { if (!Preferences.showEvents || !context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
view.warning_container.isVisible = true binding.warningContainer.isVisible = true
view.warning_title.text = context.getString(R.string.settings_show_events_as_glance_provider_error) binding.warningTitle.text = context.getString(R.string.settings_show_events_as_glance_provider_error)
view.warning_container.setOnClickListener { binding.warningContainer.setOnClickListener {
dismiss() dismiss()
EventBus.getDefault().post(MainFragment.ChangeTabEvent(1)) EventBus.getDefault().post(MainFragment.ChangeTabEvent(1))
} }
} else { } else {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
} }
} }
private fun checkNotificationPermission(view: View) { private fun checkNotificationPermission() {
when { when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> { ActiveNotificationsHelper.checkNotificationAccess(context) -> {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
MediaPlayerHelper.updatePlayingMediaInfo(context) MediaPlayerHelper.updatePlayingMediaInfo(context)
} }
Preferences.showMusic -> { Preferences.showMusic -> {
view.warning_container.isVisible = true binding.warningContainer.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_notification_access) binding.warningTitle.text = context.getString(R.string.settings_request_notification_access)
view.warning_container.setOnClickListener { binding.warningContainer.setOnClickListener {
context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")) context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
} }
} }
else -> { else -> {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
} }
} }
statusCallback?.invoke() statusCallback?.invoke()
} }
private fun checkLastNotificationsPermission(view: View) { private fun checkLastNotificationsPermission() {
when { when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> { ActiveNotificationsHelper.checkNotificationAccess(context) -> {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
} }
Preferences.showNotifications -> { Preferences.showNotifications -> {
view.warning_container.isVisible = true binding.warningContainer.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_last_notification_access) binding.warningTitle.text = context.getString(R.string.settings_request_last_notification_access)
view.warning_container.setOnClickListener { binding.warningContainer.setOnClickListener {
context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")) context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
} }
} }
else -> { else -> {
view.warning_container.isVisible = false binding.warningContainer.isVisible = false
} }
} }
statusCallback?.invoke() statusCallback?.invoke()
} }
private fun checkFitnessPermission(view: View) { private fun checkFitnessPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION) Manifest.permission.ACTIVITY_RECOGNITION)
) { ) {
@ -299,10 +305,10 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
} else if (Preferences.showDailySteps) { } else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(context) ActivityDetectionReceiver.unregisterFence(context)
view.warning_container.isVisible = true binding.warningContainer.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_fitness_access) binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
view.warning_container.setOnClickListener { binding.warningContainer.setOnClickListener {
requireFitnessPermission(view) requireFitnessPermission()
} }
} else { } else {
ActivityDetectionReceiver.unregisterFence(context) ActivityDetectionReceiver.unregisterFence(context)
@ -310,36 +316,36 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
statusCallback?.invoke() statusCallback?.invoke()
} }
private fun checkGoogleFitConnection(view: View) { private fun checkGoogleFitConnection() {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context) val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account, if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS ActivityDetectionReceiver.FITNESS_OPTIONS
)) { )) {
view.warning_container.isVisible = true binding.warningContainer.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_fitness_access) binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
view.warning_container.setOnClickListener { binding.warningContainer.setOnClickListener {
GoogleSignIn.requestPermissions( GoogleSignIn.requestPermissions(
context, context,
1, 1,
account, account,
ActivityDetectionReceiver.FITNESS_OPTIONS) ActivityDetectionReceiver.FITNESS_OPTIONS)
} }
view.action_connect_to_google_fit.isVisible = true binding.actionConnectToGoogleFit.isVisible = true
view.action_disconnect_to_google_fit.isVisible = false binding.actionDisconnectToGoogleFit.isVisible = false
view.action_connect_to_google_fit.setOnClickListener { binding.actionConnectToGoogleFit.setOnClickListener {
GoogleSignIn.requestPermissions( GoogleSignIn.requestPermissions(
context, context,
1, 1,
account, account,
ActivityDetectionReceiver.FITNESS_OPTIONS) ActivityDetectionReceiver.FITNESS_OPTIONS)
} }
view.action_disconnect_to_google_fit.setOnClickListener(null) binding.actionDisconnectToGoogleFit.setOnClickListener(null)
view.google_fit_status_label.text = context.getString(R.string.google_fit_account_not_connected) binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_not_connected)
} else { } else {
view.action_connect_to_google_fit.isVisible = false binding.actionConnectToGoogleFit.isVisible = false
view.action_disconnect_to_google_fit.isVisible = true binding.actionDisconnectToGoogleFit.isVisible = true
view.action_connect_to_google_fit.setOnClickListener(null) binding.actionConnectToGoogleFit.setOnClickListener(null)
view.action_disconnect_to_google_fit.setOnClickListener { binding.actionDisconnectToGoogleFit.setOnClickListener {
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder( GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension( GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS ActivityDetectionReceiver.FITNESS_OPTIONS
@ -347,11 +353,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
show() show()
} }
} }
view.google_fit_status_label.text = context.getString(R.string.google_fit_account_connected) binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_connected)
} }
} }
private fun requireFitnessPermission(view: View) { private fun requireFitnessPermission() {
Dexter.withContext(context) Dexter.withContext(context)
.withPermissions( .withPermissions(
"com.google.android.gms.permission.ACTIVITY_RECOGNITION", "com.google.android.gms.permission.ACTIVITY_RECOGNITION",
@ -359,7 +365,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else "com.google.android.gms.permission.ACTIVITY_RECOGNITION" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else "com.google.android.gms.permission.ACTIVITY_RECOGNITION"
).withListener(object: MultiplePermissionsListener { ).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
checkFitnessPermission(view) checkFitnessPermission()
} }
override fun onPermissionRationaleShouldBeShown( override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?, permissions: MutableList<PermissionRequest>?,

View File

@ -1,43 +1,41 @@
package com.tommasoberlose.anotherwidget.components package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.view.View import android.view.LayoutInflater
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuBinding
import com.tommasoberlose.anotherwidget.databinding.IconPackMenuItemBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.header
import kotlinx.android.synthetic.main.fragment_weather_settings.*
import kotlinx.android.synthetic.main.icon_pack_menu_item.view.*
class IconPackSelector(context: Context, private val header: String? = null) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) { class IconPackSelector(context: Context, private val header: String? = null) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var binding = BottomSheetMenuBinding.inflate(LayoutInflater.from(context))
override fun show() { override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_menu, null)
// Header // Header
view.header.isVisible = header != null binding.header.isVisible = header != null
view.header_text.text = header ?: "" binding.headerText.text = header ?: ""
view.warning_text.isVisible = false binding.warningText.isVisible = false
// Menu // Menu
for (item in Constants.WeatherIconPack.values()) { for (item in Constants.WeatherIconPack.values()) {
val itemView = View.inflate(context, R.layout.icon_pack_menu_item, null) val itemBinding = IconPackMenuItemBinding.inflate(LayoutInflater.from(context))
itemView.label.text = context.getString(R.string.settings_weather_icon_pack_default).format(item.value + 1) itemBinding.label.text = context.getString(R.string.settings_weather_icon_pack_default).format(item.rawValue + 1)
itemView.isSelected = item.value == Preferences.weatherIconPack itemBinding.root.isSelected = item.rawValue == Preferences.weatherIconPack
itemView.icon_1.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01d", item.value))) itemBinding.icon1.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01d", item.rawValue)))
itemView.icon_2.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01n", item.value))) itemBinding.icon2.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01n", item.rawValue)))
itemView.icon_3.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "10d", item.value))) itemBinding.icon3.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "10d", item.rawValue)))
itemView.icon_4.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "09n", item.value))) itemBinding.icon4.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "09n", item.rawValue)))
listOf<ImageView>(itemView.icon_1, itemView.icon_2, itemView.icon_3, itemView.icon_4).forEach { listOf<ImageView>(itemBinding.icon1, itemBinding.icon2, itemBinding.icon3, itemBinding.icon4).forEach {
if (item == Constants.WeatherIconPack.MINIMAL) { if (item == Constants.WeatherIconPack.MINIMAL) {
it.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimaryText)) it.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimaryText))
} else { } else {
@ -45,13 +43,13 @@ class IconPackSelector(context: Context, private val header: String? = null) : B
} }
} }
itemView.setOnClickListener { itemBinding.root.setOnClickListener {
Preferences.weatherIconPack = item.value Preferences.weatherIconPack = item.rawValue
this.dismiss() this.dismiss()
} }
view.menu.addView(itemView) binding.menu.addView(itemBinding.root)
} }
setContentView(view) setContentView(binding.root)
super.show() super.show()
} }
} }

View File

@ -1,11 +1,11 @@
package com.tommasoberlose.anotherwidget.components package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.view.View import android.view.LayoutInflater
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import kotlinx.android.synthetic.main.bottom_sheet_dialog.view.* import com.tommasoberlose.anotherwidget.databinding.BottomSheetDialogBinding
typealias DialogCallback = () -> Unit typealias DialogCallback = () -> Unit
@ -20,6 +20,8 @@ class MaterialBottomSheetDialog(
private var positiveCallback: DialogCallback? = null private var positiveCallback: DialogCallback? = null
private var negativeCallback: DialogCallback? = null private var negativeCallback: DialogCallback? = null
private var binding = BottomSheetDialogBinding.inflate(LayoutInflater.from(context))
fun setPositiveButton(label: String? = context.getString(android.R.string.ok), callback: DialogCallback? = null): MaterialBottomSheetDialog { fun setPositiveButton(label: String? = context.getString(android.R.string.ok), callback: DialogCallback? = null): MaterialBottomSheetDialog {
positiveButtonLabel = label positiveButtonLabel = label
positiveCallback = callback positiveCallback = callback
@ -33,30 +35,28 @@ class MaterialBottomSheetDialog(
} }
override fun show() { override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_dialog, null)
// Header // Header
view.title.isVisible = title != null binding.title.isVisible = title != null
view.title.text = title ?: "" binding.title.text = title ?: ""
view.message.isVisible = message != null binding.message.isVisible = message != null
view.message.text = message ?: "" binding.message.text = message ?: ""
view.action_positive.isVisible = positiveButtonLabel != null binding.actionPositive.isVisible = positiveButtonLabel != null
view.action_positive.text = positiveButtonLabel ?: "" binding.actionPositive.text = positiveButtonLabel ?: ""
view.action_positive.setOnClickListener { binding.actionPositive.setOnClickListener {
positiveCallback?.invoke() positiveCallback?.invoke()
this.dismiss() this.dismiss()
} }
view.action_negative.isVisible = negativeButtonLabel != null binding.actionNegative.isVisible = negativeButtonLabel != null
view.action_negative.text = negativeButtonLabel ?: "" binding.actionNegative.text = negativeButtonLabel ?: ""
view.action_negative.setOnClickListener { binding.actionNegative.setOnClickListener {
negativeCallback?.invoke() negativeCallback?.invoke()
this.dismiss() this.dismiss()
} }
setContentView(view) setContentView(binding.root)
super.show() super.show()
} }

View File

@ -0,0 +1,31 @@
package com.tommasoberlose.anotherwidget.components
import android.view.View
class OnSingleClickListener : View.OnClickListener {
private val onClickListener: View.OnClickListener
constructor(listener: View.OnClickListener) {
onClickListener = listener
}
constructor(listener: (View) -> Unit) {
onClickListener = View.OnClickListener { listener.invoke(it) }
}
override fun onClick(v: View) {
val currentTimeMillis = System.currentTimeMillis()
if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
previousClickTimeMillis = currentTimeMillis
onClickListener.onClick(v)
}
}
companion object {
private const val DELAY_MILLIS = 200L
private var previousClickTimeMillis = 0L
}
}

View File

@ -2,7 +2,6 @@ package com.tommasoberlose.anotherwidget.global
object Actions { object Actions {
const val ACTION_EXTRA_OPEN_WEATHER_PROVIDER = "ACTION_EXTRA_OPEN_WEATHER_PROVIDER" const val ACTION_EXTRA_OPEN_WEATHER_PROVIDER = "ACTION_EXTRA_OPEN_WEATHER_PROVIDER"
const val ACTION_EXTRA_DISABLE_GPS_NOTIFICATION = "ACTION_EXTRA_DISABLE_GPS_NOTIFICATION"
const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.TIME_UPDATE" const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.TIME_UPDATE"
const val ACTION_ALARM_UPDATE = "com.tommasoberlose.anotherwidget.action.ALARM_UPDATE" const val ACTION_ALARM_UPDATE = "com.tommasoberlose.anotherwidget.action.ALARM_UPDATE"
@ -14,4 +13,6 @@ object Actions {
const val ACTION_REPORT_CRASH = "com.tommasoberlose.anotherwidget.action.REPORT_CRASH" const val ACTION_REPORT_CRASH = "com.tommasoberlose.anotherwidget.action.REPORT_CRASH"
const val ACTION_CLEAR_NOTIFICATION = "com.tommasoberlose.anotherwidget.action.CLEAR_NOTIFICATION" const val ACTION_CLEAR_NOTIFICATION = "com.tommasoberlose.anotherwidget.action.CLEAR_NOTIFICATION"
const val ACTION_UPDATE_GREETINGS = "com.tommasoberlose.anotherwidget.action.UPDATE_GREETINGS" const val ACTION_UPDATE_GREETINGS = "com.tommasoberlose.anotherwidget.action.UPDATE_GREETINGS"
const val ACTION_REFRESH = "com.tommasoberlose.anotherwidget.action.REFRESH"
} }

View File

@ -1,7 +1,5 @@
package com.tommasoberlose.anotherwidget.global package com.tommasoberlose.anotherwidget.global
import java.text.SimpleDateFormat
object Constants { object Constants {
const val RESULT_CODE_CUSTOM_LOCATION = 45 const val RESULT_CODE_CUSTOM_LOCATION = 45
const val RESULT_APP_NAME = "RESULT_APP_NAME" const val RESULT_APP_NAME = "RESULT_APP_NAME"
@ -11,14 +9,14 @@ object Constants {
const val CUSTOM_FONT_DOWNLOADED = 2 const val CUSTOM_FONT_DOWNLOADED = 2
const val CUSTOM_FONT_DOWNLOAD_NEW = 3 const val CUSTOM_FONT_DOWNLOAD_NEW = 3
enum class ClockBottomMargin(val value: Int) { enum class ClockBottomMargin(val rawValue: Int) {
NONE(0), NONE(0),
SMALL(1), SMALL(1),
MEDIUM(2), MEDIUM(2),
LARGE(3) LARGE(3)
} }
enum class SecondRowTopMargin(val value: Int) { enum class SecondRowTopMargin(val rawValue: Int) {
NONE(0), NONE(0),
SMALL(1), SMALL(1),
MEDIUM(2), MEDIUM(2),
@ -41,13 +39,13 @@ object Constants {
} }
} }
enum class WidgetUpdateFrequency(val value: Int) { enum class WidgetUpdateFrequency(val rawValue: Int) {
LOW(0), LOW(0),
DEFAULT(1), DEFAULT(1),
HIGH(2) HIGH(2)
} }
enum class WeatherProvider(val value: Int) { enum class WeatherProvider(val rawValue: Int) {
OPEN_WEATHER(0), OPEN_WEATHER(0),
WEATHER_BIT(1), WEATHER_BIT(1),
WEATHER_API(2), WEATHER_API(2),
@ -57,12 +55,12 @@ object Constants {
YR(6); YR(6);
companion object { companion object {
private val map = WeatherProvider.values().associateBy(WeatherProvider::value) private val map = WeatherProvider.values().associateBy(WeatherProvider::rawValue)
fun fromInt(type: Int) = map[type] fun fromInt(type: Int) = map[type]
} }
} }
enum class GlanceNotificationTimer(val value: Int) { enum class GlanceNotificationTimer(val rawValue: Int) {
HALF_MINUTE(0), HALF_MINUTE(0),
ONE_MINUTE(1), ONE_MINUTE(1),
FIVE_MINUTES(2), FIVE_MINUTES(2),
@ -71,15 +69,21 @@ object Constants {
WHEN_DISMISSED(5); WHEN_DISMISSED(5);
companion object { companion object {
private val map = values().associateBy(GlanceNotificationTimer::value) private val map = values().associateBy(GlanceNotificationTimer::rawValue)
fun fromInt(type: Int) = map[type] fun fromInt(type: Int) = map[type]
} }
} }
enum class WeatherIconPack(val value: Int) { enum class WeatherIconPack(val rawValue: Int) {
DEFAULT(0), DEFAULT(0),
MINIMAL(1), MINIMAL(1),
COOL(2), COOL(2),
GOOGLE_NEWS(3) GOOGLE_NEWS(3)
} }
enum class WidgetAlign(val rawValue: Int) {
LEFT(0),
RIGHT(1),
CENTER(2)
}
} }

View File

@ -1,8 +1,11 @@
package com.tommasoberlose.anotherwidget.global package com.tommasoberlose.anotherwidget.global
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate.* import androidx.appcompat.app.AppCompatDelegate.*
import androidx.core.os.ConfigurationCompat
import com.chibatching.kotpref.KotprefModel import com.chibatching.kotpref.KotprefModel
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.utils.isMetric
object Preferences : KotprefModel() { object Preferences : KotprefModel() {
override val commitAllPropertiesByDefault: Boolean = true override val commitAllPropertiesByDefault: Boolean = true
@ -14,8 +17,8 @@ object Preferences : KotprefModel() {
var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false) var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false)
var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "") var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "")
var weatherTemp by floatPref(key = "PREF_WEATHER_TEMP", default = 0f) var weatherTemp by floatPref(key = "PREF_WEATHER_TEMP", default = 0f)
var weatherTempUnit by stringPref(key = "PREF_WEATHER_TEMP_UNIT", default = "F") var weatherTempUnit by stringPref(key = "PREF_WEATHER_TEMP_UNIT", default = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) "C" else "F")
var weatherRealTempUnit by stringPref(key = "PREF_WEATHER_REAL_TEMP_UNIT", default = "F") var weatherRealTempUnit by stringPref(key = "PREF_WEATHER_REAL_TEMP_UNIT", default = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) "C" else "F")
var calendarAllDay by booleanPref(key = "PREF_CALENDAR_ALL_DAY", default = true) var calendarAllDay by booleanPref(key = "PREF_CALENDAR_ALL_DAY", default = true)
var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "") var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "")
@ -43,14 +46,14 @@ object Preferences : KotprefModel() {
var weatherProviderApiWeatherApi by stringPref(default = "") var weatherProviderApiWeatherApi by stringPref(default = "")
var weatherProviderApiWeatherBit by stringPref(default = "") var weatherProviderApiWeatherBit by stringPref(default = "")
var weatherProviderApiAccuweather by stringPref(default = "") var weatherProviderApiAccuweather by stringPref(default = "")
var weatherProvider by intPref(default = Constants.WeatherProvider.OPEN_WEATHER.value) var weatherProvider by intPref(default = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) Constants.WeatherProvider.YR.rawValue else Constants.WeatherProvider.WEATHER_GOV.rawValue)
var weatherProviderError by stringPref(default = "") var weatherProviderError by stringPref(default = "")
var weatherProviderLocationError by stringPref(default = "") var weatherProviderLocationError by stringPref(default = "")
var eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "") var eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "")
var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "") var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "")
var openEventDetails by booleanPref(default = true) var openEventDetails by booleanPref(default = true)
var widgetUpdateFrequency by intPref(default = Constants.WidgetUpdateFrequency.DEFAULT.value) var widgetUpdateFrequency by intPref(default = Constants.WidgetUpdateFrequency.DEFAULT.rawValue)
var textGlobalColor by stringPref(key = "PREF_TEXT_COLOR", default = "#FFFFFF") var textGlobalColor by stringPref(key = "PREF_TEXT_COLOR", default = "#FFFFFF")
var textGlobalAlpha by stringPref(default = "FF") var textGlobalAlpha by stringPref(default = "FF")
@ -79,14 +82,18 @@ object Preferences : KotprefModel() {
var showAMPMIndicator by booleanPref(default = true) var showAMPMIndicator by booleanPref(default = true)
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.value) var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// Clock
var altTimezoneLabel by stringPref(default = "")
var altTimezoneId by stringPref(default = "")
// Global // Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f) var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f) 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 = 90f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.value) var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.value) var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false) var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "") var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "") var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")
@ -103,21 +110,21 @@ object Preferences : KotprefModel() {
var customFontName by stringPref(default = "") var customFontName by stringPref(default = "")
var customFontVariant by stringPref(default = "regular") var customFontVariant by stringPref(default = "regular")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true) var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showNextEventOnMultipleLines by booleanPref(default = false)
var showDividers by booleanPref(default = true) var showDividers by booleanPref(default = true)
var widgetAlign by intPref(default = Constants.WidgetAlign.CENTER.rawValue)
// Settings // Settings
var showWallpaper by booleanPref(default = true) var showWallpaper by booleanPref(default = true)
var showBigClockWarning by booleanPref(default = true)
var showWeatherWarning by booleanPref(default = true)
var showPreview by booleanPref(default = true) var showPreview by booleanPref(default = true)
var showXiaomiWarning by booleanPref(default = true) var showXiaomiWarning by booleanPref(default = true)
// Glance // Glance
var showGlance by booleanPref(default = true)
var enabledGlanceProviderOrder by stringPref(default = "") var enabledGlanceProviderOrder by stringPref(default = "")
var customNotes by stringPref(default = "") var customNotes by stringPref(default = "")
var showNextAlarm by booleanPref(default = true) var showNextAlarm by booleanPref(default = false)
var showBatteryCharging by booleanPref(default = false) var showBatteryCharging by booleanPref(default = false)
var isBatteryLevelLow by booleanPref(default = false) var isBatteryLevelLow by booleanPref(default = false)
var isCharging by booleanPref(default = false) var isCharging by booleanPref(default = false)
@ -125,7 +132,7 @@ object Preferences : KotprefModel() {
var showDailySteps by booleanPref(default = false) var showDailySteps by booleanPref(default = false)
var showGreetings by booleanPref(default = false) var showGreetings by booleanPref(default = false)
var showNotifications by booleanPref(default = false) var showNotifications by booleanPref(default = false)
var hideNotificationAfter by intPref(default = Constants.GlanceNotificationTimer.ONE_MINUTE.value) var hideNotificationAfter by intPref(default = Constants.GlanceNotificationTimer.ONE_MINUTE.rawValue)
var lastNotificationId by intPref(default = -1) var lastNotificationId by intPref(default = -1)
var lastNotificationTitle by stringPref(default = "") var lastNotificationTitle by stringPref(default = "")
@ -133,11 +140,11 @@ object Preferences : KotprefModel() {
var lastNotificationPackage by stringPref(default = "") var lastNotificationPackage by stringPref(default = "")
var showMusic by booleanPref(default = false) var showMusic by booleanPref(default = false)
var mediaInfoFormat by stringPref(default = "") var mediaInfoFormat by stringPref(default = MediaPlayerHelper.DEFAULT_MEDIA_INFO_FORMAT)
var mediaPlayerTitle by stringPref(default = "") var mediaPlayerTitle by stringPref(default = "")
var mediaPlayerAlbum by stringPref(default = "") var mediaPlayerAlbum by stringPref(default = "")
var mediaPlayerArtist by stringPref(default = "") var mediaPlayerArtist by stringPref(default = "")
var mediaPlayerPackage by stringPref(default = "") var mediaPlayerPackage by stringPref(default = IntentHelper.DO_NOTHING_OPTION)
var musicPlayersFilter by stringPref(default = "") var musicPlayersFilter by stringPref(default = "")
var appNotificationsFilter by stringPref(default = "") var appNotificationsFilter by stringPref(default = "")

View File

@ -1,29 +1,16 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.Manifest import android.Manifest
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.ContentUris
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.CalendarContract import android.provider.CalendarContract
import android.util.Log
import com.tommasoberlose.anotherwidget.services.EventListenerJob import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver import com.tommasoberlose.anotherwidget.services.UpdateCalendarService
import com.tommasoberlose.anotherwidget.services.UpdateCalendarJob
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.* import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
/** /**
@ -32,7 +19,7 @@ import kotlin.collections.ArrayList
object CalendarHelper { object CalendarHelper {
fun updateEventList(context: Context) { fun updateEventList(context: Context) {
UpdateCalendarJob.enqueueWork(context, Intent()) UpdateCalendarService.enqueueWork(context)
} }
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> { fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {

View File

@ -1,15 +1,27 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.annotation.SuppressLint 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.graphics.Color
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toast
import kotlin.math.roundToInt import kotlin.math.roundToInt
object ColorHelper { object ColorHelper {
fun getFontColor(isDark: Boolean): Int { fun getFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format(if (!isDark) Preferences.textGlobalAlpha else Preferences.textGlobalAlphaDark, (if (!isDark) Preferences.textGlobalColor else Preferences.textGlobalColorDark).replace("#", ""))) Color.parseColor("#%s%s".format(if (!isDark) Preferences.textGlobalAlpha else Preferences.textGlobalAlphaDark,
(if (!isDark) Preferences.textGlobalColor else Preferences.textGlobalColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#FFFFFFFF") Color.parseColor("#FFFFFFFF")
} }
@ -33,7 +45,10 @@ object ColorHelper {
fun getSecondaryFontColor(isDark: Boolean): Int { fun getSecondaryFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format((if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark), (if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark).replace("#", ""))) Color.parseColor("#%s%s".format((if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark),
(if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#FFFFFFFF") Color.parseColor("#FFFFFFFF")
} }
@ -57,7 +72,10 @@ object ColorHelper {
fun getClockFontColor(isDark: Boolean): Int { fun getClockFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format((if (!isDark) Preferences.clockTextAlpha else Preferences.clockTextAlphaDark), (if (!isDark) Preferences.clockTextColor else Preferences.clockTextColorDark).replace("#", ""))) Color.parseColor("#%s%s".format((if (!isDark) Preferences.clockTextAlpha else Preferences.clockTextAlphaDark),
(if (!isDark) Preferences.clockTextColor else Preferences.clockTextColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#FFFFFFFF") Color.parseColor("#FFFFFFFF")
} }
@ -81,7 +99,10 @@ object ColorHelper {
fun getBackgroundColor(isDark: Boolean): Int { fun getBackgroundColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format((if (!isDark) Preferences.backgroundCardAlpha else Preferences.backgroundCardAlphaDark), (if (!isDark) Preferences.backgroundCardColor else Preferences.backgroundCardColorDark).replace("#", ""))) Color.parseColor("#%s%s".format((if (!isDark) Preferences.backgroundCardAlpha else Preferences.backgroundCardAlphaDark),
(if (!isDark) Preferences.backgroundCardColor else Preferences.backgroundCardColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#00000000") Color.parseColor("#00000000")
} }
@ -123,4 +144,50 @@ object ColorHelper {
val hexValue = this.toInt(16).toDouble() val hexValue = this.toInt(16).toDouble()
return (hexValue * 100 / 255).roundToInt() return (hexValue * 100 / 255).roundToInt()
} }
fun String.isColor(): Boolean {
return try {
Color.parseColor(this)
true
} catch (iae: IllegalArgumentException) {
iae.printStackTrace()
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

@ -45,49 +45,49 @@ object GlanceProviderHelper {
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> { Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_next_alarm_title), context.getString(R.string.settings_show_next_alarm_title),
R.drawable.round_access_alarm R.drawable.round_access_alarm_24
) )
} }
Constants.GlanceProviderId.PLAYING_SONG -> { Constants.GlanceProviderId.PLAYING_SONG -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_music_title), context.getString(R.string.settings_show_music_title),
R.drawable.round_music_note R.drawable.round_music_note_24
) )
} }
Constants.GlanceProviderId.CUSTOM_INFO -> { Constants.GlanceProviderId.CUSTOM_INFO -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_custom_notes_title), context.getString(R.string.settings_custom_notes_title),
R.drawable.round_sticky_note_2 R.drawable.round_sticky_note_2_24
) )
} }
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> { Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_low_battery_level_title), context.getString(R.string.settings_low_battery_level_title),
R.drawable.round_battery_charging_full R.drawable.round_battery_charging_full_24
) )
} }
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> { Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_daily_steps_title), context.getString(R.string.settings_daily_steps_title),
R.drawable.round_favorite_border R.drawable.round_favorite_border_24
) )
} }
Constants.GlanceProviderId.NOTIFICATIONS -> { Constants.GlanceProviderId.NOTIFICATIONS -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_notifications_title), context.getString(R.string.settings_show_notifications_title),
R.drawable.round_notifications R.drawable.round_notifications_24
) )
} }
Constants.GlanceProviderId.GREETINGS -> { Constants.GlanceProviderId.GREETINGS -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_greetings_title), context.getString(R.string.settings_show_greetings_title),
R.drawable.round_history_edu R.drawable.round_history_edu_24
) )
} }
Constants.GlanceProviderId.EVENTS -> { Constants.GlanceProviderId.EVENTS -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_events_as_glance_provider_title), context.getString(R.string.settings_show_events_as_glance_provider_title),
R.drawable.round_event_note R.drawable.round_event_note_24
) )
} }
} }
@ -101,7 +101,7 @@ object GlanceProviderHelper {
val eventRepository = EventRepository(context) val eventRepository = EventRepository(context)
BatteryHelper.updateBatteryInfo(context) BatteryHelper.updateBatteryInfo(context)
val showGlance = Preferences.showGlance && (eventRepository.getEventsCount() == 0 || !Preferences.showEvents || Preferences.showEventsAsGlanceProvider) val showGlance = (eventRepository.getEventsCount() == 0 || !Preferences.showEvents || Preferences.showEventsAsGlanceProvider)
&& ( && (
(Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) || (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) ||
(Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") || (Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") ||

View File

@ -20,7 +20,7 @@ object ImageHelper {
else -> 0f * factor else -> 0f * factor
}, resources.displayMetrics) }, resources.displayMetrics)
if (originalView.drawable != null) { if (originalView.drawable != null && originalView.drawable.intrinsicWidth > 0 && originalView.drawable.intrinsicHeight > 0) {
val btm = originalView.drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true) val btm = originalView.drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true)
val comb = Bitmap.createBitmap(btm) val comb = Bitmap.createBitmap(btm)
val shadowBitmap = generateShadowBitmap(context, cElevation, btm, factor) val shadowBitmap = generateShadowBitmap(context, cElevation, btm, factor)
@ -30,7 +30,7 @@ object ImageHelper {
canvas.drawColor(Color.TRANSPARENT) canvas.drawColor(Color.TRANSPARENT)
canvas.save() canvas.save()
val rect = Rect() val rect = Rect()
val bounds = originalView.drawable.copyBounds() // val bounds = originalView.drawable.copyBounds()
canvas.getClipBounds(rect) canvas.getClipBounds(rect)
rect.inset(-2 * getBlurRadius(context, cElevation).toInt(), -2 * getBlurRadius(context, cElevation).toInt()) rect.inset(-2 * getBlurRadius(context, cElevation).toInt(), -2 * getBlurRadius(context, cElevation).toInt())
canvas.save() canvas.save()

View File

@ -12,8 +12,10 @@ import android.provider.CalendarContract
import android.provider.CalendarContract.Events import android.provider.CalendarContract.Events
import android.util.Log import android.util.Log
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast import com.tommasoberlose.anotherwidget.utils.toast
import java.util.* import java.util.*
@ -21,6 +23,10 @@ import java.util.*
object IntentHelper { object IntentHelper {
const val DEFAULT_OPTION = ""
const val DO_NOTHING_OPTION = "DO_NOTHING"
const val REFRESH_WIDGET_OPTION = "REFRESH_WIDGET"
fun getWidgetUpdateIntent(context: Context): Intent { fun getWidgetUpdateIntent(context: Context): Intent {
val widgetManager = AppWidgetManager.getInstance(context) val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java) val widgetComponent = ComponentName(context, MainWidget::class.java)
@ -31,6 +37,13 @@ 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 { 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=$address")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
@ -47,17 +60,21 @@ object IntentHelper {
fun getWeatherIntent(context: Context): Intent { fun getWeatherIntent(context: Context): Intent {
return when (Preferences.weatherAppPackage) { return when (Preferences.weatherAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT) addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity") data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline") component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
setClassName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
@ -79,14 +96,17 @@ object IntentHelper {
.appendPath(Calendar.getInstance().timeInMillis.toString()) .appendPath(Calendar.getInstance().timeInMillis.toString())
.build() .build()
return when (Preferences.calendarAppPackage) { return when (Preferences.calendarAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
data = calendarUri data = calendarUri
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
@ -164,14 +184,17 @@ object IntentHelper {
fun getClockIntent(context: Context): Intent { fun getClockIntent(context: Context): Intent {
return when (Preferences.clockAppPackage) { return when (Preferences.clockAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(AlarmClock.ACTION_SHOW_ALARMS).apply { Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
@ -191,7 +214,7 @@ object IntentHelper {
fun getMusicIntent(context: Context): Intent { fun getMusicIntent(context: Context): Intent {
return when (Preferences.mediaPlayerPackage) { return when (Preferences.mediaPlayerPackage) {
"" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
else -> { else -> {

View File

@ -12,17 +12,38 @@ import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.NotificationListener import com.tommasoberlose.anotherwidget.receivers.NotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import java.lang.Exception import java.lang.Exception
object MediaPlayerHelper { object MediaPlayerHelper {
const val MEDIA_INFO_TITLE = "%TITLE"
const val MEDIA_INFO_ARTIST = "%ARTIST"
const val MEDIA_INFO_ALBUM = "%ALBUM"
const val DEFAULT_MEDIA_INFO_FORMAT = "%TITLE, %ARTIST"
fun isSomeonePlaying(context: Context) = Preferences.showMusic && ActiveNotificationsHelper.checkNotificationAccess(context) && Preferences.mediaPlayerTitle != "" fun isSomeonePlaying(context: Context) = Preferences.showMusic && ActiveNotificationsHelper.checkNotificationAccess(context) && Preferences.mediaPlayerTitle != ""
fun getMediaInfo(): String { fun getMediaInfo(format: String = Preferences.mediaInfoFormat, title: String = Preferences.mediaPlayerTitle, artist: String = Preferences.mediaPlayerArtist, album: String = Preferences.mediaPlayerAlbum): String {
return if (Preferences.mediaPlayerArtist == "") { return when (format) {
Preferences.mediaPlayerTitle "",
} else { DEFAULT_MEDIA_INFO_FORMAT -> {
"%s, %s".format(Preferences.mediaPlayerTitle, Preferences.mediaPlayerArtist) if (Preferences.mediaPlayerArtist == "") {
} Preferences.mediaPlayerTitle
} else {
DEFAULT_MEDIA_INFO_FORMAT.replace(MEDIA_INFO_TITLE, title)
.replace(MEDIA_INFO_ARTIST, artist)
.replace(MEDIA_INFO_ALBUM, album)
.replace("\\n", System.getProperty("line.separator") ?: " ")
}
}
else -> {
format.replace(MEDIA_INFO_TITLE, title)
.replace(MEDIA_INFO_ARTIST, artist)
.replace(MEDIA_INFO_ALBUM, album)
.replace("\\n", System.getProperty("line.separator") ?: " ")
}
}
} }
fun updatePlayingMediaInfo(context: Context) { fun updatePlayingMediaInfo(context: Context) {
@ -49,15 +70,24 @@ object MediaPlayerHelper {
isSomeonePlaying = true isSomeonePlaying = true
if (metadata != null) { if (metadata != null) {
Preferences.bulk { Preferences.bulk {
mediaPlayerTitle = ignoreExceptions {
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)?.toString() mediaPlayerTitle =
?: "" metadata.getText(MediaMetadata.METADATA_KEY_TITLE)
mediaPlayerArtist = ?.toString()
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)?.toString() ?: ""
?: "" }
mediaPlayerAlbum = ignoreExceptions {
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)?.toString() mediaPlayerArtist =
?: "" metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)
?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)
?.toString()
?: ""
}
} }
} }

View File

@ -63,10 +63,20 @@ object SettingsStringHelper {
} }
fun getVariantLabel(context: Context, variant: String): String = when { fun getVariantLabel(context: Context, variant: String): String = when {
variant == "italic" -> context.getString(R.string.font_italic)
variant.contains("100") && variant.contains("italic") -> context.getString(R.string.font_100_italic)
variant.contains("200") && variant.contains("italic") -> context.getString(R.string.font_200_italic)
variant.contains("300") && variant.contains("italic") -> context.getString(R.string.font_300_italic)
variant.contains("400") && variant.contains("italic") -> context.getString(R.string.font_400_italic)
variant.contains("500") && variant.contains("italic") -> context.getString(R.string.font_500_italic)
variant.contains("600") && variant.contains("italic") -> context.getString(R.string.font_600_italic)
variant.contains("700") && variant.contains("italic") -> context.getString(R.string.font_700_italic)
variant.contains("800") && variant.contains("italic") -> context.getString(R.string.font_800_italic)
variant.contains("900") && variant.contains("italic") -> context.getString(R.string.font_900_italic)
variant == "regular" || variant.contains("400") -> context.getString(R.string.font_400)
variant.contains("100") -> context.getString(R.string.font_100) variant.contains("100") -> context.getString(R.string.font_100)
variant.contains("200") -> context.getString(R.string.font_200) variant.contains("200") -> context.getString(R.string.font_200)
variant.contains("300") -> context.getString(R.string.font_300) variant.contains("300") -> context.getString(R.string.font_300)
variant.contains("regular") || variant.contains("400") -> context.getString(R.string.font_400)
variant.contains("500") -> context.getString(R.string.font_500) variant.contains("500") -> context.getString(R.string.font_500)
variant.contains("600") -> context.getString(R.string.font_600) variant.contains("600") -> context.getString(R.string.font_600)
variant.contains("700") -> context.getString(R.string.font_700) variant.contains("700") -> context.getString(R.string.font_700)
@ -86,13 +96,13 @@ object SettingsStringHelper {
difference <= 0 -> { difference <= 0 -> {
return "" return ""
} }
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.value && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> { 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() 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.DEFAULT.value && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> { 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() 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.LOW.value -> { TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.LOW.rawValue -> {
return context.getString(R.string.soon) return context.getString(R.string.soon)
} }
TimeUnit.MILLISECONDS.toHours(difference) < 1 -> { TimeUnit.MILLISECONDS.toHours(difference) < 1 -> {

View File

@ -2,22 +2,15 @@ package com.tommasoberlose.anotherwidget.helpers
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.os.Build
import android.util.Log
import com.chibatching.kotpref.Kotpref import com.chibatching.kotpref.Kotpref
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.services.LocationService
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDarkTheme import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
/** /**
@ -31,26 +24,8 @@ object WeatherHelper {
val networkApi = WeatherNetworkApi(context) val networkApi = WeatherNetworkApi(context)
if (Preferences.customLocationAdd != "") { if (Preferences.customLocationAdd != "") {
networkApi.updateWeather() networkApi.updateWeather()
} else if (context.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION)) { } else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener { task -> LocationService.requestNewLocation(context)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} }
} }
@ -139,97 +114,97 @@ object WeatherHelper {
return when (icon) { return when (icon) {
"01d" -> { "01d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.clear_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.clear_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.clear_day_4
else -> if (context.isDarkTheme()) R.drawable.clear_day_5 else R.drawable.clear_day_5_light else -> if (context.isDarkTheme()) R.drawable.clear_day_5 else R.drawable.clear_day_5_light
} }
} }
"02d" -> { "02d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.partly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.partly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.partly_cloudy_4
else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_5 else R.drawable.partly_cloudy_5_light else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_5 else R.drawable.partly_cloudy_5_light
} }
} }
"03d" -> { "03d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.mostly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.mostly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.mostly_cloudy_4
else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_5 else R.drawable.mostly_cloudy_5_light else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_5 else R.drawable.mostly_cloudy_5_light
} }
} }
"04d" -> { "04d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.cloudy_weather_4
else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light
} }
} }
"09d" -> { "09d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.storm_weather_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.storm_weather_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.storm_weather_day_4
else -> if (context.isDarkTheme()) R.drawable.storm_weather_day_5 else R.drawable.storm_weather_day_5_light else -> if (context.isDarkTheme()) R.drawable.storm_weather_day_5 else R.drawable.storm_weather_day_5_light
} }
} }
"10d" -> { "10d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rainy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rainy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.rainy_day_4
else -> if (context.isDarkTheme()) R.drawable.rainy_day_5 else R.drawable.rainy_day_5_light else -> if (context.isDarkTheme()) R.drawable.rainy_day_5 else R.drawable.rainy_day_5_light
} }
} }
"11d" -> { "11d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.thunder_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.thunder_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.thunder_day_4
else -> if (context.isDarkTheme()) R.drawable.thunder_day_5 else R.drawable.thunder_day_5_light else -> if (context.isDarkTheme()) R.drawable.thunder_day_5 else R.drawable.thunder_day_5_light
} }
} }
"13d" -> { "13d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.snow_day_4
else -> if (context.isDarkTheme()) R.drawable.snow_day_5 else R.drawable.snow_day_5_light else -> if (context.isDarkTheme()) R.drawable.snow_day_5 else R.drawable.snow_day_5_light
} }
} }
"50d" -> { "50d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.haze_day_4
else -> if (context.isDarkTheme()) R.drawable.haze_day_5 else R.drawable.haze_day_5_light else -> if (context.isDarkTheme()) R.drawable.haze_day_5 else R.drawable.haze_day_5_light
} }
} }
"80d" -> { "80d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.windy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.windy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.windy_day_4
else -> if (context.isDarkTheme()) R.drawable.windy_day_5 else R.drawable.windy_day_5_light else -> if (context.isDarkTheme()) R.drawable.windy_day_5 else R.drawable.windy_day_5_light
} }
} }
"81d" -> { "81d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_day_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rain_snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_day_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rain_snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_day_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.rain_snow_day_4
else -> if (context.isDarkTheme()) R.drawable.rain_snow_day_5 else R.drawable.rain_snow_day_5_light else -> if (context.isDarkTheme()) R.drawable.rain_snow_day_5 else R.drawable.rain_snow_day_5_light
} }
} }
"82d" -> { "82d" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.haze_weather_4
else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light
} }
} }
@ -238,97 +213,97 @@ object WeatherHelper {
"01n" -> { "01n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.clear_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.clear_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.clear_night_4
else -> if (context.isDarkTheme()) R.drawable.clear_night_5 else R.drawable.clear_night_5_light else -> if (context.isDarkTheme()) R.drawable.clear_night_5 else R.drawable.clear_night_5_light
} }
} }
"02n" -> { "02n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.partly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.partly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.partly_cloudy_night_4
else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_night_5 else R.drawable.partly_cloudy_night_5_light else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_night_5 else R.drawable.partly_cloudy_night_5_light
} }
} }
"03n" -> { "03n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.mostly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.mostly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.mostly_cloudy_night_4
else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_night_5 else R.drawable.mostly_cloudy_night_5_light else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_night_5 else R.drawable.mostly_cloudy_night_5_light
} }
} }
"04n" -> { "04n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.cloudy_weather_4
else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light
} }
} }
"09n" -> { "09n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.storm_weather_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.storm_weather_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.storm_weather_night_4
else -> if (context.isDarkTheme()) R.drawable.storm_weather_night_5 else R.drawable.storm_weather_night_5_light else -> if (context.isDarkTheme()) R.drawable.storm_weather_night_5 else R.drawable.storm_weather_night_5_light
} }
} }
"10n" -> { "10n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rainy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rainy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.rainy_night_4
else -> if (context.isDarkTheme()) R.drawable.rainy_night_5 else R.drawable.rainy_night_5_light else -> if (context.isDarkTheme()) R.drawable.rainy_night_5 else R.drawable.rainy_night_5_light
} }
} }
"11n" -> { "11n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.thunder_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.thunder_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.thunder_night_4
else -> if (context.isDarkTheme()) R.drawable.thunder_night_5 else R.drawable.thunder_night_5_light else -> if (context.isDarkTheme()) R.drawable.thunder_night_5 else R.drawable.thunder_night_5_light
} }
} }
"13n" -> { "13n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.snow_night_4
else -> if (context.isDarkTheme()) R.drawable.snow_night_5 else R.drawable.snow_night_5_light else -> if (context.isDarkTheme()) R.drawable.snow_night_5 else R.drawable.snow_night_5_light
} }
} }
"50n" -> { "50n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.haze_night_4
else -> if (context.isDarkTheme()) R.drawable.haze_night_5 else R.drawable.haze_night_5_light else -> if (context.isDarkTheme()) R.drawable.haze_night_5 else R.drawable.haze_night_5_light
} }
} }
"80n" -> { "80n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.windy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.windy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.windy_night_4
else -> if (context.isDarkTheme()) R.drawable.windy_night_5 else R.drawable.windy_night_5_light else -> if (context.isDarkTheme()) R.drawable.windy_night_5 else R.drawable.windy_night_5_light
} }
} }
"81n" -> { "81n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_night_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rain_snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_night_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rain_snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_night_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.rain_snow_night_4
else -> if (context.isDarkTheme()) R.drawable.rain_snow_night_5 else R.drawable.rain_snow_night_5_light else -> if (context.isDarkTheme()) R.drawable.rain_snow_night_5 else R.drawable.rain_snow_night_5_light
} }
} }
"82n" -> { "82n" -> {
when (style) { when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3 Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2 Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4 Constants.WeatherIconPack.GOOGLE_NEWS.rawValue -> R.drawable.haze_weather_4
else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light
} }
} }

View File

@ -25,9 +25,8 @@ object WidgetHelper {
) { ) {
fun getWidgetsSize(widgetId: Int): Pair<Int, Int> { fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
val isPortrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT val width = getWidgetWidth(widgetId)
val width = getWidgetWidth(isPortrait, widgetId) val height = getWidgetHeight(widgetId)
val height = getWidgetHeight(isPortrait, widgetId)
val widthInPx = context.dip(width) val widthInPx = context.dip(width)
val heightInPx = context.dip(height) val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx) FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
@ -35,9 +34,9 @@ object WidgetHelper {
return widthInPx to heightInPx return widthInPx to heightInPx
} }
private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) private fun getWidgetWidth(widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) private fun getWidgetHeight(widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
private fun getWidgetSizeInDp(widgetId: Int, key: String): Int = private fun getWidgetSizeInDp(widgetId: Int, key: String): Int =
appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0) appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0)

View File

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

View File

@ -2,6 +2,7 @@ package com.tommasoberlose.anotherwidget.network
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.chibatching.kotpref.Kotpref
import com.google.gson.internal.LinkedTreeMap import com.google.gson.internal.LinkedTreeMap
import com.haroldadmin.cnradapter.NetworkResponse import com.haroldadmin.cnradapter.NetworkResponse
import com.haroldadmin.cnradapter.executeWithRetry import com.haroldadmin.cnradapter.executeWithRetry
@ -26,6 +27,7 @@ import java.util.*
class WeatherNetworkApi(val context: Context) { class WeatherNetworkApi(val context: Context) {
suspend fun updateWeather() { suspend fun updateWeather() {
Kotpref.init(context)
Preferences.weatherProviderError = "-" Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
@ -344,7 +346,7 @@ class WeatherNetworkApi(val context: Context) {
private suspend fun useAccuweatherProvider(context: Context) { private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") { if (Preferences.weatherProviderApiAccuweather != "") {
val repository = AccuweatherRepository() // val repository = AccuweatherRepository()
// when (val response = repository.getWeather()) { // when (val response = repository.getWeather()) {
// is NetworkResponse.Success -> { // is NetworkResponse.Success -> {

View File

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

View File

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

View File

@ -138,10 +138,8 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
val endTime: Long = cal.timeInMillis val endTime: Long = cal.timeInMillis
val readRequest = DataReadRequest.Builder() val readRequest = DataReadRequest.Builder()
.aggregate( .aggregate(DataType.TYPE_STEP_COUNT_DELTA)
DataType.TYPE_STEP_COUNT_DELTA, .aggregate(DataType.AGGREGATE_STEP_COUNT_DELTA)
DataType.AGGREGATE_STEP_COUNT_DELTA
)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS) .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.bucketByTime(1, TimeUnit.DAYS) .bucketByTime(1, TimeUnit.DAYS)
.build() .build()

View File

@ -9,6 +9,7 @@ import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.google.gson.Gson
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
@ -37,21 +38,17 @@ class NotificationListener : NotificationListenerService() {
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) { if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
Preferences.lastNotificationId = sbn.id Preferences.lastNotificationId = sbn.id
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: "" Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { try {
try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Preferences.lastNotificationIcon = sbn.notification.smallIcon.resId Preferences.lastNotificationIcon = sbn.notification.smallIcon.resId
} catch (ex: Exception) { } else {
Preferences.lastNotificationIcon = 0 @Suppress("DEPRECATION")
}
Preferences.lastNotificationPackage = sbn.notification.smallIcon.resPackage
} else {
try {
Preferences.lastNotificationIcon = sbn.notification.icon Preferences.lastNotificationIcon = sbn.notification.icon
} catch (ex: Exception) {
Preferences.lastNotificationIcon = 0
} }
Preferences.lastNotificationPackage = sbn.packageName } catch (ex: Exception) {
Preferences.lastNotificationIcon = 0
} }
Preferences.lastNotificationPackage = sbn.packageName
MainWidget.updateWidget(this) MainWidget.updateWidget(this)
setTimeout(this) setTimeout(this)
} }

View File

@ -5,19 +5,16 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.core.app.AlarmManagerCompat
import androidx.core.content.ContextCompat.getSystemService
import com.tommasoberlose.anotherwidget.db.EventRepository import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.BatteryHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.joda.time.Period import org.joda.time.Period
import java.util.* import java.util.*
@ -55,6 +52,14 @@ class UpdatesReceiver : BroadcastReceiver() {
Actions.ACTION_UPDATE_GREETINGS -> { Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
} }
Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context)
}
}
} }
} }
@ -102,7 +107,7 @@ class UpdatesReceiver : BroadcastReceiver() {
if (diff.hours == 0) { if (diff.hours == 0) {
var minutes = 0 var minutes = 0
when (Preferences.widgetUpdateFrequency) { when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.value -> { Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
minutes = when { minutes = when {
diff.minutes > 50 -> 50 diff.minutes > 50 -> 50
diff.minutes > 30 -> 30 diff.minutes > 30 -> 30
@ -110,7 +115,7 @@ class UpdatesReceiver : BroadcastReceiver() {
else -> 0 else -> 0
} }
} }
Constants.WidgetUpdateFrequency.HIGH.value -> { Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
minutes = diff.minutes - (diff.minutes % 5) minutes = diff.minutes - (diff.minutes % 5)
} }
} }

View File

@ -6,6 +6,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.IntentHelper import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.utils.toast import com.tommasoberlose.anotherwidget.utils.toast
@ -15,19 +16,20 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) { if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) {
try { try {
context.startActivity(IntentHelper.getWeatherIntent(context)) if (Preferences.weatherAppPackage == IntentHelper.REFRESH_WIDGET_OPTION) {
context.sendBroadcast(IntentHelper.getWeatherIntent(context))
} else {
context.startActivity(IntentHelper.getWeatherIntent(context))
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
val uri = Uri.parse("http://www.google.com/search?q=weather")
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try { try {
context.applicationContext.startActivity(IntentHelper.getWeatherIntent(context.applicationContext)) context.startActivity(i)
} catch (e: Exception) { } catch (ignored: Exception) {
val uri = Uri.parse("http://www.google.com/#q=weather") context.toast(context.getString(R.string.error_opening_app))
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try {
context.startActivity(i)
} catch (ignored: Exception) {
context.toast(context.getString(R.string.error_opening_app))
}
} }
} }
} }

View File

@ -0,0 +1,124 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.app.job.JobScheduler
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.IBinder
import android.util.Log
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
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 kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
class LocationService : Service() {
private var job: Job? = null
override fun onCreate() {
super.onCreate()
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
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
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
stopSelf()
}
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
companion object {
const val LOCATION_ACCESS_NOTIFICATION_ID = 28465
@JvmStatic
fun requestNewLocation(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, LocationService::class.java))
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun getLocationAccessNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.location_access_notification_channel_id),
getString(R.string.location_access_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.location_access_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@LocationService, getString(R.string.location_access_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.location_access_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@LocationService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@LocationService, 0, Intent(this@LocationService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -1,135 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import android.util.Log
import androidx.core.app.JobIntentService
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
class UpdateCalendarJob : JobIntentService() {
companion object {
private const val jobId = 1200
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, UpdateCalendarJob::class.java, jobId, work)
}
}
override fun onHandleWork(intent: Intent) {
val eventRepository = EventRepository(this)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
val now = Calendar.getInstance()
val begin = 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 {
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
if (!checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
} else {
try {
val provider = CalendarProvider(this)
val data = provider.getInstances(begin.timeInMillis, limit.timeInMillis)
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)
}
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId.toInt(),
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventRepository.saveEvents(
sortedEvents
)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
}
UpdatesReceiver.setUpdates(this)
MainWidget.updateWidget(this)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
eventRepository.close()
}
}

View File

@ -0,0 +1,214 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.collections.ArrayList
class UpdateCalendarService : Service() {
companion object {
const val CALENDAR_SYNC_NOTIFICATION_ID = 28468
fun enqueueWork(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, UpdateCalendarService::class.java))
}
}
override fun onCreate() {
super.onCreate()
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
}
private var job: Job? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
val eventRepository = EventRepository(this@UpdateCalendarService)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
val now = Calendar.getInstance()
val begin = 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 {
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
if (!checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
} else {
try {
val provider = CalendarProvider(this@UpdateCalendarService)
val data = provider.getInstances(begin.timeInMillis, limit.timeInMillis)
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)
}
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
val endDate = Calendar.getInstance()
endDate.timeInMillis = instance.end
val isAllDay = e.allDay || (
startDate.get(Calendar.MILLISECOND) == 0
&& startDate.get(Calendar.SECOND) == 0
&& startDate.get(Calendar.MINUTE) == 0
&& startDate.get(Calendar.HOUR_OF_DAY) == 0
&& endDate.get(Calendar.MILLISECOND) == 0
&& endDate.get(Calendar.SECOND) == 0
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId.toInt(),
allDay = isAllDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventRepository.saveEvents(
sortedEvents
)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
}
UpdatesReceiver.setUpdates(this@UpdateCalendarService)
MainWidget.updateWidget(this@UpdateCalendarService)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
eventRepository.close()
stopSelf()
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
private fun getCalendarSyncNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.calendar_sync_notification_channel_id),
getString(R.string.calendar_sync_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.calendar_sync_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@UpdateCalendarService, getString(R.string.calendar_sync_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.calendar_sync_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@UpdateCalendarService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@UpdateCalendarService, 0, Intent(this@UpdateCalendarService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -1,148 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ResolveInfo
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import kotlinx.android.synthetic.main.activity_choose_application.*
import kotlinx.android.synthetic.main.activity_choose_application.action_back
import kotlinx.android.synthetic.main.activity_choose_application.clear_search
import kotlinx.android.synthetic.main.activity_choose_application.list_view
import kotlinx.android.synthetic.main.activity_choose_application.loader
import kotlinx.android.synthetic.main.activity_choose_application.search
import kotlinx.android.synthetic.main.activity_music_players_filter.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.SlimAdapterEx
class ChooseApplicationActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: ChooseApplicationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(ChooseApplicationViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityChooseApplicationBinding>(this, R.layout.activity_choose_application)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapterEx.create()
adapter
.register<String>(R.layout.application_info_layout) { _, injector ->
injector
.text(R.id.text, getString(R.string.default_name))
.image(R.id.icon, R.drawable.round_add_to_home_screen)
.with<ImageView>(R.id.icon) {
it.scaleX = 0.8f
it.scaleY = 0.8f
it.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimaryText), android.graphics.PorterDuff.Mode.MULTIPLY)
}
.clicked(R.id.item) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, "")
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, "")
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
injector
.text(R.id.text, item.loadLabel(viewModel.pm))
.with<ImageView>(R.id.icon) {
Glide
.with(this)
.load(item.loadIcon(viewModel.pm))
.centerCrop()
.into(it)
}
injector.clicked(R.id.item) {
saveApp(item)
}
}
.attachTo(list_view)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityChooseApplicationBinding, viewModel: ChooseApplicationViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.appList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
clear_search.isVisible = search.isNotBlank()
})
}
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
list
} else {
list.filter {
it.loadLabel(viewModel.pm).contains(search, true)
}
}
withContext(Dispatchers.Main) {
adapter.updateData(listOf("Default") + filteredList)
loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
clear_search.setOnClickListener {
viewModel.searchInput.value = ""
}
}
private fun saveApp(app: ResolveInfo) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, app.loadLabel(viewModel.pm))
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, app.activityInfo.packageName)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}

View File

@ -1,189 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.location.Address
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomDateBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.CustomDateViewModel
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.activity_custom_date.*
import kotlinx.android.synthetic.main.activity_custom_location.action_back
import kotlinx.android.synthetic.main.activity_custom_location.list_view
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class CustomDateActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomDateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomDateViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCustomDateBinding>(this, R.layout.activity_custom_date)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_date_example_item) { item, injector ->
injector
.text(R.id.custom_date_example_format, item)
.text(R.id.custom_date_example_value, SimpleDateFormat(item, Locale.getDefault()).format(DATE.time))
}
.attachTo(list_view)
adapter.updateData(
listOf(
"d", "dd", "EE", "EEEE", "MM", "MMM", "MMMM", "yy", "yyyy"
)
)
setupListener()
subscribeUi(binding, viewModel)
date_format.requestFocus()
}
private var formatJob: Job? = null
private fun subscribeUi(binding: ActivityCustomDateBinding, viewModel: CustomDateViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.dateInput.observe(this, Observer { dateFormat ->
formatJob?.cancel()
formatJob = lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
loader.visibility = View.VISIBLE
}
delay(200)
var text = if (dateFormat != "") {
try {
SimpleDateFormat(dateFormat, Locale.getDefault()).format(DATE.time)
} catch (e: Exception) {
ERROR_STRING
}
} else {
ERROR_STRING
}
if (viewModel.isDateCapitalize.value == true) {
text = text.getCapWordString()
}
if (viewModel.isDateUppercase.value == true) {
text = text.toUpperCase(Locale.getDefault())
}
withContext(Dispatchers.Main) {
loader.visibility = View.INVISIBLE
date_format_value.text = text
}
}
})
viewModel.isDateCapitalize.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
viewModel.isDateUppercase.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
}
private fun updateCapitalizeUi() {
when {
viewModel.isDateUppercase.value == true -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
action_capitalize.alpha = 1f
}
viewModel.isDateCapitalize.value == true -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.ic_capitalize))
action_capitalize.alpha = 1f
}
else -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
action_capitalize.alpha = 0.3f
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
action_capitalize.setOnClickListener {
when {
viewModel.isDateUppercase.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = false
}
viewModel.isDateCapitalize.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = true
}
else -> {
viewModel.isDateCapitalize.value = true
viewModel.isDateUppercase.value = false
}
}
}
action_capitalize.setOnLongClickListener {
toast(getString(R.string.action_capitalize_the_date))
true
}
action_date_format_info.setOnClickListener {
openURI("https://developer.android.com/reference/java/text/SimpleDateFormat")
}
}
override fun onBackPressed() {
Preferences.blockingBulk {
dateFormat = viewModel.dateInput.value ?: ""
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
super.onBackPressed()
}
companion object {
const val ERROR_STRING = "--"
val DATE: Calendar = Calendar.getInstance().apply {
set(Calendar.MONTH, 10)
set(Calendar.DAY_OF_MONTH, 1)
set(Calendar.YEAR, 1993)
}
}
}

View File

@ -1,179 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.location.Address
import android.location.Geocoder
import android.os.Build
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.Window
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.bulk
import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomLocationBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.CustomLocationViewModel
import kotlinx.android.synthetic.main.activity_custom_location.*
import kotlinx.android.synthetic.main.activity_custom_location.action_back
import kotlinx.android.synthetic.main.activity_custom_location.clear_search
import kotlinx.android.synthetic.main.activity_custom_location.list_view
import kotlinx.android.synthetic.main.activity_custom_location.loader
import kotlinx.android.synthetic.main.activity_music_players_filter.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.ThreadMode
import org.greenrobot.eventbus.Subscribe
class CustomLocationActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomLocationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomLocationViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCustomLocationBinding>(this, R.layout.activity_custom_location)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
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) {
MaterialBottomSheetDialog(this, message = getString(R.string.background_location_warning))
.setPositiveButton(getString(android.R.string.ok)) {
requirePermission()
}
.show()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
injector.text(R.id.text, item.getAddressLine(0))
injector.clicked(R.id.text) {
Preferences.bulk {
customLocationLat = item.latitude.toString()
customLocationLon = item.longitude.toString()
customLocationAdd = item.getAddressLine(0)
setResult(Activity.RESULT_OK)
finish()
}
}
}
.attachTo(list_view)
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
})
setupListener()
subscribeUi(binding, viewModel)
location.requestFocus()
}
private var searchJob: Job? = null
private fun subscribeUi(binding: ActivityCustomLocationBinding, viewModel: CustomLocationViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
loader.visibility = View.INVISIBLE
})
viewModel.locationInput.observe(this, Observer { location ->
loader.visibility = View.VISIBLE
searchJob?.cancel()
searchJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
val list = if (location == null || location == "") {
viewModel.addresses.value!!
} else {
val coder = Geocoder(this@CustomLocationActivity)
try {
coder.getFromLocationName(location, 10) as ArrayList<Address>
} catch (ignored: Exception) {
emptyList<Address>()
}
}
withContext(Dispatchers.Main) {
viewModel.addresses.value = list
loader.visibility = View.INVISIBLE
}
}
clear_search.isVisible = location.isNotBlank()
})
}
private fun requirePermission() {
Dexter.withContext(this)
.withPermissions(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else 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() {
action_back.setOnClickListener {
onBackPressed()
}
clear_search.setOnClickListener {
viewModel.locationInput.value = ""
}
}
}

View File

@ -1,52 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityIntegrationsBinding
import com.tommasoberlose.anotherwidget.ui.viewmodels.IntegrationsViewModel
import kotlinx.android.synthetic.main.activity_integrations.*
import net.idik.lib.slimadapter.SlimAdapter
class IntegrationsActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: IntegrationsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(IntegrationsViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityIntegrationsBinding>(this, R.layout.activity_integrations)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.application_info_layout) { _, injector ->
injector
.text(R.id.text, getString(R.string.default_name))
}
.attachTo(list_view)
setupListener()
subscribeUi(binding, viewModel)
}
private fun subscribeUi(binding: ActivityIntegrationsBinding, viewModel: IntegrationsViewModel) {
binding.viewModel = viewModel
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
}
}

View File

@ -1,93 +1,74 @@
package com.tommasoberlose.anotherwidget.ui.activities package com.tommasoberlose.anotherwidget.ui.activities
import android.Manifest import android.Manifest
import android.animation.ValueAnimator
import android.app.Activity import android.app.Activity
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Matrix
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.Navigation import androidx.navigation.Navigation
import com.chibatching.kotpref.Kotpref
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.tabs.TabLayoutMediator
import com.karumi.dexter.Dexter import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog import com.tommasoberlose.anotherwidget.databinding.ActivityMainBinding
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.BitmapHelper import com.tommasoberlose.anotherwidget.ui.activities.tabs.WeatherProviderActivity
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.adapters.ViewPagerAdapter
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.getCurrentWallpaper
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
class MainActivity : AppCompatActivity() {
private var mAppWidgetId: Int = -1 private var mAppWidgetId: Int = -1
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private lateinit var binding: ActivityMainBinding
private val mainNavController: NavController? by lazy { private val mainNavController: NavController? by lazy {
Navigation.findNavController( Navigation.findNavController(
this, this,
R.id.content_fragment R.id.content_fragment
) )
} }
private val settingsNavController: NavController? by lazy {
Navigation.findNavController(
this,
R.id.settings_fragment
)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java) viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding = ActivityMainBinding.inflate(layoutInflater)
controlExtras(intent) controlExtras(intent)
if (Preferences.showWallpaper) { if (Preferences.showWallpaper) {
requirePermission() requirePermission()
} }
setContentView(binding.root)
} }
override fun onBackPressed() { override fun onBackPressed() {
if (mainNavController?.currentDestination?.id == R.id.appMainFragment) { if (mainNavController?.currentDestination?.id == R.id.appMainFragment) {
if (mAppWidgetId > 0) { if (settingsNavController?.navigateUp() == false) {
addNewWidget() if (mAppWidgetId > 0) {
addNewWidget()
} else {
setResult(Activity.RESULT_OK)
finish()
}
} else { } else {
setResult(Activity.RESULT_OK) viewModel.fragmentScrollY.value = 0
finish()
} }
} else { } else {
super.onBackPressed() super.onBackPressed()
@ -110,8 +91,8 @@ class MainActivity : AppCompatActivity() {
AppWidgetManager.INVALID_APPWIDGET_ID) AppWidgetManager.INVALID_APPWIDGET_ID)
if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
action_add_widget.visibility = View.VISIBLE binding.actionAddWidget.visibility = View.VISIBLE
action_add_widget.setOnClickListener { binding.actionAddWidget.setOnClickListener {
addNewWidget() addNewWidget()
} }
} }
@ -153,4 +134,26 @@ class MainActivity : AppCompatActivity() {
}) })
.check() .check()
} }
override fun onResume() {
super.onResume()
if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
Preferences.showEvents = false
}
}
override fun onStart() {
Preferences.preferences.registerOnSharedPreferenceChangeListener(this)
super.onStart()
}
override fun onStop() {
super.onStop()
Preferences.preferences.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(p0: SharedPreferences?, p1: String?) {
MainWidget.updateWidget(this)
}
} }

View File

@ -1,152 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ResolveInfo
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityMusicPlayersFilterBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.MusicPlayersFilterViewModel
import kotlinx.android.synthetic.main.activity_music_players_filter.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import kotlin.Comparator as Comparator1
class MusicPlayersFilterActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MusicPlayersFilterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MusicPlayersFilterViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityMusicPlayersFilterBinding>(this, R.layout.activity_music_players_filter)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
injector
.text(R.id.text, item.loadLabel(viewModel.pm))
.with<ImageView>(R.id.icon) {
Glide
.with(this)
.load(item.loadIcon(viewModel.pm))
.centerCrop()
.into(it)
}
.visible(R.id.checkBox)
.clicked(R.id.item) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.clicked(R.id.checkBox) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.checked(R.id.checkBox, MediaPlayerHelper.isMusicPlayerAccepted(item.activityInfo.packageName))
}
.attachTo(list_view)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityMusicPlayersFilterBinding, viewModel: MusicPlayersFilterViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.appList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
clear_search.isVisible = search.isNotBlank()
})
viewModel.musicPlayersFilter.observe(this, {
updateList()
clear_selection.isVisible = Preferences.musicPlayersFilter != ""
})
}
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
list
} else {
list.filter {
it.loadLabel(viewModel.pm).contains(search, true)
}
}.sortedWith { app1, app2 ->
if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName) && MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName)) {
-1
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
1
} else {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
}
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
clear_search.setOnClickListener {
viewModel.searchInput.value = ""
}
clear_selection.setOnClickListener {
Preferences.musicPlayersFilter = ""
}
}
private fun toggleApp(app: ResolveInfo) {
MediaPlayerHelper.toggleMusicPlayerFilter(app.activityInfo.packageName)
}
}

View File

@ -1,191 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.content.Intent
import android.content.pm.ResolveInfo
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.BottomSheetWeatherProviderSettings
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityWeatherProviderBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.WeatherProviderViewModel
import com.tommasoberlose.anotherwidget.utils.collapse
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.reveal
import kotlinx.android.synthetic.main.activity_weather_provider.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.idik.lib.slimadapter.SlimAdapter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class WeatherProviderActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: WeatherProviderViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather_provider)
viewModel = ViewModelProvider(this).get(WeatherProviderViewModel::class.java)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<Constants.WeatherProvider>(R.layout.weather_provider_list_item) { provider, injector ->
injector
.text(R.id.text, WeatherHelper.getProviderName(this, provider))
.clicked(R.id.item) {
if (Preferences.weatherProvider != provider.value) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
}
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.value
updateListItem(oldValue)
updateListItem()
loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.clicked(R.id.radioButton) {
if (Preferences.weatherProvider != provider.value) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
}
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.value
updateListItem(oldValue)
updateListItem()
loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.checked(R.id.radioButton, provider.value == Preferences.weatherProvider)
.with<TextView>(R.id.text2) {
if (WeatherHelper.isKeyRequired(provider)) {
it.text = getString(R.string.api_key_required_message)
}
if (provider == Constants.WeatherProvider.WEATHER_GOV) {
it.text = getString(R.string.us_only_message)
}
if (provider == Constants.WeatherProvider.YR) {
it.text = getString(R.string.celsius_only_message)
}
}
.clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}.show()
}
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.value == Preferences.weatherProvider) View.VISIBLE else View.GONE)
.with<TextView>(R.id.provider_error) {
if (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") {
it.text = Preferences.weatherProviderError
it.isVisible = provider.value == Preferences.weatherProvider
} else if (Preferences.weatherProviderLocationError != "") {
it.text = Preferences.weatherProviderLocationError
it.isVisible = provider.value == Preferences.weatherProvider
} else {
it.isVisible = false
}
}
.image(R.id.action_configure, ContextCompat.getDrawable(this, if (WeatherHelper.isKeyRequired(provider)) R.drawable.round_settings else R.drawable.outline_info_white))
}.attachTo(list_view)
adapter.updateData(
Constants.WeatherProvider.values().asList()
.filter { it != Constants.WeatherProvider.HERE }
.filter { it != Constants.WeatherProvider.ACCUWEATHER }
)
setupListener()
subscribeUi(viewModel)
}
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
updateListItem()
}
viewModel.weatherProviderLocationError.observe(this) {
updateListItem()
}
}
private fun updateListItem(provider: Int = Preferences.weatherProvider) {
(adapter.data).forEachIndexed { index, item ->
if (item is Constants.WeatherProvider && item.value == provider) {
adapter.notifyItemChanged(index)
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
}
override fun onBackPressed() {
setResult(Activity.RESULT_OK)
finish()
}
override fun onResume() {
super.onResume()
EventBus.getDefault().register(this)
}
override fun onPause() {
EventBus.getDefault().unregister(this)
super.onPause()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: MainFragment.UpdateUiMessageEvent?) {
loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(list_view, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()
}
}
}

View File

@ -0,0 +1,53 @@
package com.tommasoberlose.anotherwidget.ui.activities.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityIntegrationsBinding
import com.tommasoberlose.anotherwidget.ui.viewmodels.settings.IntegrationsViewModel
import net.idik.lib.slimadapter.SlimAdapter
class IntegrationsActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: IntegrationsViewModel
private lateinit var binding: ActivityIntegrationsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(IntegrationsViewModel::class.java)
binding = ActivityIntegrationsBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.application_info_layout) { _, injector ->
injector
.text(R.id.text, getString(R.string.default_name))
}
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
setContentView(binding.root)
}
private fun subscribeUi(binding: ActivityIntegrationsBinding, viewModel: IntegrationsViewModel) {
binding.viewModel = viewModel
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
}
}

View File

@ -1,47 +1,38 @@
package com.tommasoberlose.anotherwidget.ui.activities package com.tommasoberlose.anotherwidget.ui.activities.settings
import android.app.Activity
import android.content.Intent
import android.location.Address
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.android.billingclient.api.* import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode.OK import com.android.billingclient.api.BillingClient.BillingResponseCode.OK
import com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED import com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivitySupportDevBinding import com.tommasoberlose.anotherwidget.databinding.ActivitySupportDevBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.ui.viewmodels.settings.SupportDevViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.SupportDevViewModel
import com.tommasoberlose.anotherwidget.utils.toast import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.activity_support_dev.*
import net.idik.lib.slimadapter.SlimAdapter import net.idik.lib.slimadapter.SlimAdapter
class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener { class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var viewModel: SupportDevViewModel private lateinit var viewModel: SupportDevViewModel
private lateinit var adapter: SlimAdapter private lateinit var adapter: SlimAdapter
private lateinit var binding: ActivitySupportDevBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(SupportDevViewModel::class.java) viewModel = ViewModelProvider(this).get(SupportDevViewModel::class.java)
viewModel.billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build() viewModel.billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build()
DataBindingUtil.setContentView<ActivitySupportDevBinding>(this, R.layout.activity_support_dev) binding = ActivitySupportDevBinding.inflate(layoutInflater)
list_view.setHasFixedSize(true) binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create() adapter = SlimAdapter.create()
adapter adapter
@ -62,20 +53,22 @@ class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
viewModel.purchase(this, item) viewModel.purchase(this, item)
} }
} }
.attachTo(list_view) .attachTo(binding.listView)
viewModel.openConnection() viewModel.openConnection()
subscribeUi(viewModel) subscribeUi(viewModel)
action_back.setOnClickListener { binding.actionBack.setOnClickListener {
onBackPressed() onBackPressed()
} }
setContentView(binding.root)
} }
private fun subscribeUi(viewModel: SupportDevViewModel) { private fun subscribeUi(viewModel: SupportDevViewModel) {
viewModel.products.observe(this, Observer { viewModel.products.observe(this, Observer {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
loader.isVisible = false binding.loader.isVisible = false
} }
adapter.updateData(it.sortedWith(compareBy(SkuDetails::getPriceAmountMicros))) adapter.updateData(it.sortedWith(compareBy(SkuDetails::getPriceAmountMicros)))
}) })

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.activities package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.os.Bundle import android.os.Bundle
@ -6,7 +6,6 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -16,8 +15,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityAppNotificationsFilterBinding import com.tommasoberlose.anotherwidget.databinding.ActivityAppNotificationsFilterBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.AppNotificationsViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.AppNotificationsViewModel
import kotlinx.android.synthetic.main.activity_app_notifications_filter.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter import net.idik.lib.slimadapter.SlimAdapter
@ -26,16 +24,17 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter private lateinit var adapter: SlimAdapter
private lateinit var viewModel: AppNotificationsViewModel private lateinit var viewModel: AppNotificationsViewModel
private lateinit var binding: ActivityAppNotificationsFilterBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(AppNotificationsViewModel::class.java) viewModel = ViewModelProvider(this).get(AppNotificationsViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityAppNotificationsFilterBinding>(this, R.layout.activity_app_notifications_filter) binding = ActivityAppNotificationsFilterBinding.inflate(layoutInflater)
list_view.setHasFixedSize(true) binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create() adapter = SlimAdapter.create()
adapter adapter
@ -60,12 +59,14 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
} }
.checked(R.id.checkBox, ActiveNotificationsHelper.isAppAccepted(item.activityInfo.packageName)) .checked(R.id.checkBox, ActiveNotificationsHelper.isAppAccepted(item.activityInfo.packageName))
} }
.attachTo(list_view) .attachTo(binding.listView)
setupListener() setupListener()
subscribeUi(binding, viewModel) subscribeUi(binding, viewModel)
search.requestFocus() binding.search.requestFocus()
setContentView(binding.root)
} }
private var filterJob: Job? = null private var filterJob: Job? = null
@ -76,22 +77,22 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
viewModel.appList.observe(this, Observer { viewModel.appList.observe(this, Observer {
updateList(list = it) updateList(list = it)
loader.visibility = View.INVISIBLE binding.loader.visibility = View.INVISIBLE
}) })
viewModel.searchInput.observe(this, Observer { search -> viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search) updateList(search = search)
clear_search.isVisible = search.isNotBlank() binding.clearSearch.isVisible = search.isNotBlank()
}) })
viewModel.appNotificationsFilter.observe(this, { viewModel.appNotificationsFilter.observe(this, {
updateList() updateList()
clear_selection.isVisible = Preferences.appNotificationsFilter != "" binding.clearSelection.isVisible = Preferences.appNotificationsFilter != ""
}) })
} }
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) { private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
loader.visibility = View.VISIBLE binding.loader.visibility = View.VISIBLE
filterJob?.cancel() filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) { filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) { if (list != null && list.isNotEmpty()) {
@ -117,22 +118,22 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.updateData(filteredList) adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE binding.loader.visibility = View.INVISIBLE
} }
} }
} }
} }
private fun setupListener() { private fun setupListener() {
action_back.setOnClickListener { binding.actionBack.setOnClickListener {
onBackPressed() onBackPressed()
} }
clear_search.setOnClickListener { binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = "" viewModel.searchInput.value = ""
} }
clear_selection.setOnClickListener { binding.clearSelection.setOnClickListener {
Preferences.appNotificationsFilter = "" Preferences.appNotificationsFilter = ""
} }
} }

View File

@ -0,0 +1,213 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.app.Activity
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.content.Intent
import android.content.pm.ResolveInfo
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.ChooseApplicationViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.SlimAdapterEx
class ChooseApplicationActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: ChooseApplicationViewModel
private lateinit var binding: ActivityChooseApplicationBinding
private var selectedPackage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
selectedPackage = intent.extras?.getString(Constants.RESULT_APP_PACKAGE)
viewModel = ViewModelProvider(this).get(ChooseApplicationViewModel::class.java)
binding = ActivityChooseApplicationBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapterEx.create()
adapter
.register<String>(R.layout.application_info_layout) { item, injector ->
when (item) {
IntentHelper.DO_NOTHING_OPTION -> {
injector
.text(R.id.text, getString(R.string.gestures_do_nothing))
.image(R.id.icon, R.drawable.round_no_cell_24)
.with<ImageView>(R.id.icon) {
it.scaleX = 0.8f
it.scaleY = 0.8f
it.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimaryText), android.graphics.PorterDuff.Mode.MULTIPLY)
}
.clicked(R.id.item) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, IntentHelper.DO_NOTHING_OPTION)
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, IntentHelper.DO_NOTHING_OPTION)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
.with<MaterialCardView>(R.id.item) {
it.strokeColor = ContextCompat.getColor(this, if (selectedPackage == IntentHelper.DO_NOTHING_OPTION) R.color.colorAccent else R.color.cardBorder)
it.setCardBackgroundColor(ContextCompat.getColor(this, if (selectedPackage == IntentHelper.DO_NOTHING_OPTION) R.color.colorAccent_op10 else R.color.colorPrimaryDark))
}
}
IntentHelper.REFRESH_WIDGET_OPTION -> {
injector
.text(R.id.text, getString(R.string.action_refresh_widget))
.image(R.id.icon, R.drawable.round_refresh)
.with<ImageView>(R.id.icon) {
it.scaleX = 0.8f
it.scaleY = 0.8f
it.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimaryText), android.graphics.PorterDuff.Mode.MULTIPLY)
}
.clicked(R.id.item) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, IntentHelper.REFRESH_WIDGET_OPTION)
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, IntentHelper.REFRESH_WIDGET_OPTION)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
.with<MaterialCardView>(R.id.item) {
it.strokeColor = ContextCompat.getColor(this, if (selectedPackage == IntentHelper.REFRESH_WIDGET_OPTION) R.color.colorAccent else R.color.cardBorder)
it.setCardBackgroundColor(ContextCompat.getColor(this, if (selectedPackage == IntentHelper.REFRESH_WIDGET_OPTION) R.color.colorAccent_op10 else R.color.colorPrimaryDark))
}
}
else -> {
injector
.text(R.id.text, getString(R.string.default_name))
.image(R.id.icon, R.drawable.round_add_to_home_screen_24)
.with<ImageView>(R.id.icon) {
it.scaleX = 0.8f
it.scaleY = 0.8f
it.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimaryText), android.graphics.PorterDuff.Mode.MULTIPLY)
}
.clicked(R.id.item) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, IntentHelper.DEFAULT_OPTION)
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, IntentHelper.DEFAULT_OPTION)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
.with<MaterialCardView>(R.id.item) {
it.strokeColor = ContextCompat.getColor(this, if (selectedPackage == IntentHelper.DEFAULT_OPTION) R.color.colorAccent else R.color.cardBorder)
it.setCardBackgroundColor(ContextCompat.getColor(this, if (selectedPackage == IntentHelper.DEFAULT_OPTION) R.color.colorAccent_op10 else R.color.colorPrimaryDark))
}
}
}
}
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
injector
.text(R.id.text, item.loadLabel(viewModel.pm))
.with<ImageView>(R.id.icon) {
Glide
.with(this)
.load(item.loadIcon(viewModel.pm))
.centerCrop()
.into(it)
}
.clicked(R.id.item) {
saveApp(item)
}
.with<MaterialCardView>(R.id.item) {
it.strokeColor = ContextCompat.getColor(this, if (selectedPackage == item.activityInfo.packageName) R.color.colorAccent else R.color.cardBorder)
it.setCardBackgroundColor(ContextCompat.getColor(this, if (selectedPackage == item.activityInfo.packageName) R.color.colorAccent_op10 else R.color.colorPrimaryDark))
}
}
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
binding.search.requestFocus()
setContentView(binding.root)
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityChooseApplicationBinding, viewModel: ChooseApplicationViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.appList.observe(this) {
updateList(list = it)
binding.loader.visibility = View.INVISIBLE
}
viewModel.searchInput.observe(this) { search ->
updateList(search = search)
binding.clearSearch.isVisible = search.isNotBlank()
}
}
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
binding.loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
list
} else {
list.filter {
it.loadLabel(viewModel.pm).contains(search, true)
}
}.sortedWith { app1, app2 ->
when (selectedPackage) {
app1.activityInfo.packageName -> {
-1
}
app2.activityInfo.packageName -> {
1
}
else -> {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
}
}
}
withContext(Dispatchers.Main) {
adapter.updateData(listOf(IntentHelper.DO_NOTHING_OPTION, IntentHelper.DEFAULT_OPTION, IntentHelper.REFRESH_WIDGET_OPTION) + filteredList)
binding.loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = ""
}
}
private fun saveApp(app: ResolveInfo) {
val resultIntent = Intent()
resultIntent.putExtra(Constants.RESULT_APP_NAME, app.loadLabel(viewModel.pm))
resultIntent.putExtra(Constants.RESULT_APP_PACKAGE, app.activityInfo.packageName)
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}

View File

@ -0,0 +1,183 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomDateBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.CustomDateViewModel
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class CustomDateActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomDateViewModel
private lateinit var binding: ActivityCustomDateBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomDateViewModel::class.java)
binding = ActivityCustomDateBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_date_example_item) { item, injector ->
injector
.text(R.id.custom_date_example_format, item)
.text(R.id.custom_date_example_value, SimpleDateFormat(item, Locale.getDefault()).format(
DATE.time))
}
.attachTo(binding.listView)
adapter.updateData(
listOf(
"d", "dd", "EE", "EEEE", "MM", "MMM", "MMMM", "yy", "yyyy"
)
)
setupListener()
subscribeUi(binding, viewModel)
binding.dateFormat.requestFocus()
setContentView(binding.root)
}
private var formatJob: Job? = null
private fun subscribeUi(binding: ActivityCustomDateBinding, viewModel: CustomDateViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.dateInput.observe(this, Observer { dateFormat ->
formatJob?.cancel()
formatJob = lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
binding.loader.visibility = View.VISIBLE
}
delay(200)
var text = if (dateFormat != "") {
try {
SimpleDateFormat(dateFormat, Locale.getDefault()).format(DATE.time)
} catch (e: Exception) {
ERROR_STRING
}
} else {
ERROR_STRING
}
if (viewModel.isDateCapitalize.value == true) {
text = text.getCapWordString()
}
if (viewModel.isDateUppercase.value == true) {
text = text.toUpperCase(Locale.getDefault())
}
withContext(Dispatchers.Main) {
binding.loader.visibility = View.INVISIBLE
binding.dateFormatValue.text = text
}
}
})
viewModel.isDateCapitalize.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
viewModel.isDateUppercase.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
}
private fun updateCapitalizeUi() {
when {
viewModel.isDateUppercase.value == true -> {
binding.actionCapitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
binding.actionCapitalize.alpha = 1f
}
viewModel.isDateCapitalize.value == true -> {
binding.actionCapitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.ic_capitalize))
binding.actionCapitalize.alpha = 1f
}
else -> {
binding.actionCapitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
binding.actionCapitalize.alpha = 0.3f
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.actionCapitalize.setOnClickListener {
when {
viewModel.isDateUppercase.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = false
}
viewModel.isDateCapitalize.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = true
}
else -> {
viewModel.isDateCapitalize.value = true
viewModel.isDateUppercase.value = false
}
}
}
binding.actionCapitalize.setOnLongClickListener {
toast(getString(R.string.action_capitalize_the_date))
true
}
binding.actionDateFormatInfo.setOnClickListener {
openURI("https://developer.android.com/reference/java/text/SimpleDateFormat")
}
}
override fun onBackPressed() {
Preferences.blockingBulk {
dateFormat = viewModel.dateInput.value ?: ""
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
super.onBackPressed()
}
companion object {
const val ERROR_STRING = "--"
val DATE: Calendar = Calendar.getInstance().apply {
set(Calendar.MONTH, 10)
set(Calendar.DAY_OF_MONTH, 1)
set(Calendar.YEAR, 1993)
}
}
}

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.activities package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
@ -6,6 +6,7 @@ import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
@ -13,28 +14,20 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.provider.FontRequest import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk import com.chibatching.kotpref.blockingBulk
import com.google.gson.Gson
import com.koolio.library.Font import com.koolio.library.Font
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomFontBinding import com.tommasoberlose.anotherwidget.databinding.ActivityCustomFontBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.CustomFontViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.CustomFontViewModel
import kotlinx.android.synthetic.main.activity_choose_application.*
import kotlinx.android.synthetic.main.activity_choose_application.action_back
import kotlinx.android.synthetic.main.activity_choose_application.clear_search
import kotlinx.android.synthetic.main.activity_choose_application.list_view
import kotlinx.android.synthetic.main.activity_choose_application.loader
import kotlinx.android.synthetic.main.activity_choose_application.search
import kotlinx.android.synthetic.main.activity_music_players_filter.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.diff.DefaultDiffCallback import net.idik.lib.slimadapter.diff.DefaultDiffCallback
@ -44,19 +37,17 @@ class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel private lateinit var viewModel: CustomFontViewModel
private lateinit var binding: ActivityCustomFontBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java) viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCustomFontBinding>( binding = ActivityCustomFontBinding.inflate(layoutInflater)
this,
R.layout.activity_custom_font
)
list_view.setHasFixedSize(true) binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create() adapter = SlimAdapter.create()
adapter.enableDiff(object: DefaultDiffCallback() { adapter.enableDiff(object: DefaultDiffCallback() {
@ -86,7 +77,7 @@ class CustomFontActivity : AppCompatActivity() {
injector.clicked(R.id.text) { injector.clicked(R.id.text) {
val dialog = BottomSheetMenu<String>(this, header = item) val dialog = BottomSheetMenu<String>(this, header = item)
listOf("100", "200", "regular", "500", "700", "800").forEachIndexed { index, s -> listOf("100", "200", "regular", "500", "700", "800").forEachIndexed { _, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), s) dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), s)
} }
dialog.addOnSelectItemListener { value -> dialog.addOnSelectItemListener { value ->
@ -136,7 +127,7 @@ class CustomFontActivity : AppCompatActivity() {
if (item.fontVariants.isEmpty()) { if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1) dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
} else { } else {
item.fontVariants.filter { !it.contains("italic") } item.fontVariants
.forEachIndexed { index, s -> .forEachIndexed { index, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), index) dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), index)
} }
@ -146,12 +137,14 @@ class CustomFontActivity : AppCompatActivity() {
}.show() }.show()
} }
} }
.attachTo(list_view) .attachTo(binding.listView)
setupListener() setupListener()
subscribeUi(binding, viewModel) subscribeUi(binding, viewModel)
search.requestFocus() binding.search.requestFocus()
setContentView(binding.root)
} }
private var filterJob: Job? = null private var filterJob: Job? = null
@ -162,12 +155,12 @@ class CustomFontActivity : AppCompatActivity() {
viewModel.fontList.observe(this, Observer { viewModel.fontList.observe(this, Observer {
updateList(list = it) updateList(list = it)
loader.visibility = View.INVISIBLE binding.loader.visibility = View.INVISIBLE
}) })
viewModel.searchInput.observe(this, Observer { search -> viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search) updateList(search = search)
clear_search.isVisible = search.isNotBlank() binding.clearSearch.isVisible = search.isNotBlank()
}) })
} }
@ -175,7 +168,7 @@ class CustomFontActivity : AppCompatActivity() {
list: ArrayList<Font>? = viewModel.fontList.value, list: ArrayList<Font>? = viewModel.fontList.value,
search: String? = viewModel.searchInput.value search: String? = viewModel.searchInput.value
) { ) {
loader.visibility = View.VISIBLE binding.loader.visibility = View.VISIBLE
filterJob?.cancel() filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) { filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) { if (list != null && list.isNotEmpty()) {
@ -209,18 +202,18 @@ class CustomFontActivity : AppCompatActivity() {
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.updateData(filteredList) adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE binding.loader.visibility = View.INVISIBLE
} }
} }
} }
} }
private fun setupListener() { private fun setupListener() {
action_back.setOnClickListener { binding.actionBack.setOnClickListener {
onBackPressed() onBackPressed()
} }
clear_search.setOnClickListener { binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = "" viewModel.searchInput.value = ""
} }
} }

View File

@ -0,0 +1,157 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.Manifest
import android.app.Activity
import android.location.Address
import android.location.Geocoder
import android.os.Bundle
import android.util.Log
import com.tommasoberlose.anotherwidget.R
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.bulk
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomLocationBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.CustomLocationViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
class CustomLocationActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomLocationViewModel
private lateinit var binding: ActivityCustomLocationBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomLocationViewModel::class.java)
binding = ActivityCustomLocationBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_location_item) { _, injector ->
injector
.text(R.id.text, getString(R.string.custom_location_gps))
.clicked(R.id.text) {
requirePermission()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
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()
}
}
}
.attachTo(binding.listView)
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
})
setupListener()
subscribeUi(binding, viewModel)
binding.location.requestFocus()
setContentView(binding.root)
}
private var searchJob: Job? = null
private fun subscribeUi(binding: ActivityCustomLocationBinding, viewModel: CustomLocationViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.addresses.observe(this, Observer {
adapter.updateData(listOf("Default") + it)
binding.loader.visibility = View.INVISIBLE
})
viewModel.locationInput.observe(this, Observer { location ->
binding.loader.visibility = View.VISIBLE
searchJob?.cancel()
searchJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
val list = if (location == null || location == "") {
viewModel.addresses.value!!
} else {
val coder = Geocoder(this@CustomLocationActivity)
try {
coder.getFromLocationName(location, 10) as ArrayList<Address>
} catch (ignored: Exception) {
emptyList<Address>()
}
}
withContext(Dispatchers.Main) {
viewModel.addresses.value = list
binding.loader.visibility = View.INVISIBLE
}
}
binding.clearSearch.isVisible = location.isNotBlank()
})
}
private fun 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()
}
binding.clearSearch.setOnClickListener {
viewModel.locationInput.value = ""
}
}
}

View File

@ -0,0 +1,109 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityMediaInfoFormatBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.MediaInfoFormatViewModel
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class MediaInfoFormatActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MediaInfoFormatViewModel
private lateinit var binding: ActivityMediaInfoFormatBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MediaInfoFormatViewModel::class.java)
binding = ActivityMediaInfoFormatBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_date_example_item) { item, injector ->
injector
.text(R.id.custom_date_example_format, item)
.text(
R.id.custom_date_example_value, MediaPlayerHelper.getMediaInfo(item, EXAMPLE_TITLE, EXAMPLE_ARTIST, EXAMPLE_ALBUM))
}
.attachTo(binding.listView)
adapter.updateData(
listOf(
MediaPlayerHelper.MEDIA_INFO_TITLE, MediaPlayerHelper.MEDIA_INFO_ARTIST, MediaPlayerHelper.MEDIA_INFO_ALBUM
)
)
setupListener()
subscribeUi(binding, viewModel)
binding.mediaInfoFormatInput.requestFocus()
setContentView(binding.root)
}
private var formatJob: Job? = null
private fun subscribeUi(binding: ActivityMediaInfoFormatBinding, viewModel: MediaInfoFormatViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.mediaInfoFormatInput.observe(this) { mediaInfoFormatInput ->
formatJob?.cancel()
formatJob = lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
binding.loader.visibility = View.VISIBLE
}
delay(200)
val text = MediaPlayerHelper.getMediaInfo(mediaInfoFormatInput, EXAMPLE_TITLE, EXAMPLE_ARTIST, EXAMPLE_ALBUM)
withContext(Dispatchers.Main) {
binding.loader.visibility = View.INVISIBLE
binding.mediaInfoFormatInputValue.text = text
}
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
}
override fun onBackPressed() {
Preferences.blockingBulk {
mediaInfoFormat = viewModel.mediaInfoFormatInput.value ?: ""
}
super.onBackPressed()
}
companion object {
const val EXAMPLE_TITLE = "Thunderstruck"
const val EXAMPLE_ARTIST = "AC/DC"
const val EXAMPLE_ALBUM = "The Razors Edge"
}
}

View File

@ -0,0 +1,144 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.content.pm.ResolveInfo
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.tommasoberlose.anotherwidget.databinding.ActivityMusicPlayersFilterBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.MusicPlayersFilterViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
class MusicPlayersFilterActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MusicPlayersFilterViewModel
private lateinit var binding: ActivityMusicPlayersFilterBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MusicPlayersFilterViewModel::class.java)
binding = ActivityMusicPlayersFilterBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
injector
.text(R.id.text, item.loadLabel(viewModel.pm))
.with<ImageView>(R.id.icon) {
Glide
.with(this)
.load(item.loadIcon(viewModel.pm))
.centerCrop()
.into(it)
}
.visible(R.id.checkBox)
.clicked(R.id.item) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.clicked(R.id.checkBox) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.checked(R.id.checkBox, MediaPlayerHelper.isMusicPlayerAccepted(item.activityInfo.packageName))
}
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
binding.search.requestFocus()
setContentView(binding.root)
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityMusicPlayersFilterBinding, viewModel: MusicPlayersFilterViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.appList.observe(this) {
updateList(list = it)
binding.loader.visibility = View.INVISIBLE
}
viewModel.searchInput.observe(this) { search ->
updateList(search = search)
binding.clearSearch.isVisible = search.isNotBlank()
}
viewModel.musicPlayersFilter.observe(this) {
updateList()
binding.clearSelection.isVisible = Preferences.musicPlayersFilter != ""
}
}
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
binding.loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
list
} else {
list.filter {
it.loadLabel(viewModel.pm).contains(search, true)
}
}.sortedWith { app1, app2 ->
if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName) && MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName)) {
-1
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
1
} else {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
}
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
binding.loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = ""
}
binding.clearSelection.setOnClickListener {
Preferences.musicPlayersFilter = ""
}
}
private fun toggleApp(app: ResolveInfo) {
MediaPlayerHelper.toggleMusicPlayerFilter(app.activityInfo.packageName)
}
}

View File

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

View File

@ -0,0 +1,174 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetWeatherProviderSettings
import com.tommasoberlose.anotherwidget.databinding.ActivityWeatherProviderBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.WeatherProviderViewModel
import kotlinx.coroutines.launch
import net.idik.lib.slimadapter.SlimAdapter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class WeatherProviderActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: WeatherProviderViewModel
private lateinit var binding: ActivityWeatherProviderBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(WeatherProviderViewModel::class.java)
binding = ActivityWeatherProviderBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<Constants.WeatherProvider>(R.layout.weather_provider_list_item) { provider, injector ->
injector
.text(R.id.text, WeatherHelper.getProviderName(this, provider))
.clicked(R.id.item) {
if (Preferences.weatherProvider != provider.rawValue) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
}
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.rawValue
updateListItem(oldValue)
updateListItem()
binding.loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.clicked(R.id.radioButton) {
if (Preferences.weatherProvider != provider.rawValue) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
}
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.rawValue
updateListItem(oldValue)
updateListItem()
binding.loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.checked(R.id.radioButton, provider.rawValue == Preferences.weatherProvider)
.with<TextView>(R.id.text2) {
if (WeatherHelper.isKeyRequired(provider)) {
it.text = getString(R.string.api_key_required_message)
}
if (provider == Constants.WeatherProvider.WEATHER_GOV) {
it.text = getString(R.string.us_only_message)
}
if (provider == Constants.WeatherProvider.YR) {
it.text = getString(R.string.celsius_only_message)
}
}
.clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}.show()
}
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.rawValue == Preferences.weatherProvider) View.VISIBLE else View.GONE)
.with<TextView>(R.id.provider_error) {
if (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") {
it.text = Preferences.weatherProviderError
it.isVisible = provider.rawValue == Preferences.weatherProvider
} else if (Preferences.weatherProviderLocationError != "") {
it.text = Preferences.weatherProviderLocationError
it.isVisible = provider.rawValue == Preferences.weatherProvider
} else {
it.isVisible = false
}
}
.image(R.id.action_configure, ContextCompat.getDrawable(this, if (WeatherHelper.isKeyRequired(provider)) R.drawable.round_settings_24 else R.drawable.outline_info_24))
}.attachTo(binding.listView)
adapter.updateData(
Constants.WeatherProvider.values().asList()
.filter { it != Constants.WeatherProvider.HERE }
.filter { it != Constants.WeatherProvider.ACCUWEATHER }
)
setupListener()
subscribeUi(viewModel)
setContentView(binding.root)
}
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
updateListItem()
}
viewModel.weatherProviderLocationError.observe(this) {
updateListItem()
}
}
private fun updateListItem(provider: Int = Preferences.weatherProvider) {
(adapter.data).forEachIndexed { index, item ->
if (item is Constants.WeatherProvider && item.rawValue == provider) {
adapter.notifyItemChanged(index)
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
}
override fun onBackPressed() {
setResult(Activity.RESULT_OK)
finish()
}
override fun onResume() {
super.onResume()
EventBus.getDefault().register(this)
}
override fun onPause() {
EventBus.getDefault().unregister(this)
super.onPause()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: MainFragment.UpdateUiMessageEvent?) {
binding.loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(binding.listView, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()
}
}
}

View File

@ -3,7 +3,7 @@ package com.tommasoberlose.anotherwidget.ui.adapters
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.tommasoberlose.anotherwidget.ui.fragments.* import com.tommasoberlose.anotherwidget.ui.fragments.tabs.*
class ViewPagerAdapter(fragmentActivity: FragmentActivity) : class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) { FragmentStateAdapter(fragmentActivity) {
@ -12,11 +12,11 @@ class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return when (position) { return when (position) {
1 -> CalendarTabFragment.newInstance() 1 -> CalendarFragment.newInstance()
2 -> WeatherTabFragment.newInstance() 2 -> WeatherFragment.newInstance()
3 -> ClockTabFragment.newInstance() 3 -> ClockFragment.newInstance()
4 -> GlanceTabFragment.newInstance() 4 -> GlanceTabFragment.newInstance()
else -> GeneralTabFragment.newInstance() else -> LayoutFragment.newInstance()
} }
} }
} }

View File

@ -1,456 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.provider.CalendarContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.bulk
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.models.CalendarSelector
import com.tommasoberlose.anotherwidget.databinding.FragmentCalendarSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.fragment_calendar_settings.*
import kotlinx.android.synthetic.main.fragment_calendar_settings.scrollView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.Comparator
class CalendarTabFragment : Fragment() {
companion object {
fun newInstance() = CalendarTabFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentCalendarSettingsBinding>(inflater, R.layout.fragment_calendar_settings, container, false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
show_all_day_toggle.isChecked = Preferences.calendarAllDay
show_only_busy_events_toggle.isChecked = Preferences.showOnlyBusyEvents
show_diff_time_toggle.isChecked = Preferences.showDiffTime
show_multiple_events_toggle.isChecked = Preferences.showNextEvent
setupListener()
}
private fun subscribeUi(
binding: FragmentCalendarSettingsBinding,
viewModel: MainViewModel
) {
binding.isCalendarEnabled = Preferences.showEvents
binding.isDiffEnabled = Preferences.showDiffTime || !Preferences.showEvents
viewModel.showEvents.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
binding.isCalendarEnabled = it
if (it) {
CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
}
binding.isDiffEnabled = Preferences.showDiffTime || !it
}
checkReadEventsPermission()
updateCalendar()
})
viewModel.calendarAllDay.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
all_day_label?.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.secondRowInformation.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_row_info_label?.text = getString(SettingsStringHelper.getSecondRowInfoString(it))
}
})
viewModel.showDiffTime.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_diff_time_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
binding.isDiffEnabled = it || !Preferences.showEvents
}
})
viewModel.widgetUpdateFrequency.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
widget_update_frequency_label?.text = when (it) {
Constants.WidgetUpdateFrequency.HIGH.value -> getString(R.string.settings_widget_update_frequency_high)
Constants.WidgetUpdateFrequency.DEFAULT.value -> getString(R.string.settings_widget_update_frequency_default)
Constants.WidgetUpdateFrequency.LOW.value -> getString(R.string.settings_widget_update_frequency_low)
else -> ""
}
}
})
viewModel.showUntil.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_until_label?.text = getString(SettingsStringHelper.getShowUntilString(it))
}
updateCalendar()
})
viewModel.showNextEvent.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_multiple_events_label?.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.calendarAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
calendar_app_label?.text = when {
Preferences.calendarAppName != "" -> Preferences.calendarAppName
else -> {
if (IntentHelper.getCalendarIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_calendar_app
)
} else {
getString(R.string.nothing)
}
}
}
}
})
viewModel.openEventDetails.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
open_event_details_label?.text = if (it) getString(R.string.default_event_app) else getString(R.string.default_calendar_app)
}
})
}
private fun setupListener() {
action_show_events.setOnClickListener {
Preferences.showEvents = !Preferences.showEvents
if (Preferences.showEvents) {
requirePermission()
}
}
show_events_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showEvents = enabled
if (Preferences.showEvents) {
requirePermission()
}
}
action_filter_calendar.setOnClickListener {
val calendarSelectorList: List<CalendarSelector> = CalendarHelper.getCalendarList(requireContext()).map {
CalendarSelector(
it.id,
it.displayName,
it.accountName
)
}.sortedWith(Comparator { cal1, cal2 ->
when {
cal1.accountName != cal2.accountName -> {
cal1.accountName.compareTo(cal2.accountName)
}
cal1.accountName == cal1.name -> {
-1
}
cal2.accountName == cal2.name -> {
1
}
else -> {
cal1.name.compareTo(cal2.name)
}
}
})
if (calendarSelectorList.isNotEmpty()) {
val filteredCalendarIds = CalendarHelper.getFilteredCalendarIdList()
val visibleCalendarIds = calendarSelectorList.map { it.id }.filter { id: Long -> !filteredCalendarIds.contains(id) }
val dialog = BottomSheetMenu<Long>(requireContext(), header = getString(R.string.settings_filter_calendar_subtitle), isMultiSelection = true)
.setSelectedValues(visibleCalendarIds)
calendarSelectorList.indices.forEach { index ->
if (index == 0 || calendarSelectorList[index].accountName != calendarSelectorList[index - 1].accountName) {
dialog.addItem(calendarSelectorList[index].accountName)
}
dialog.addItem(
if (calendarSelectorList[index].name == calendarSelectorList[index].accountName) getString(R.string.main_calendar) else calendarSelectorList[index].name,
calendarSelectorList[index].id
)
}
dialog.addOnMultipleSelectItemListener { values ->
CalendarHelper.filterCalendar(calendarSelectorList.map { it.id }.filter { !values.contains(it) })
updateCalendar()
}.show()
} else {
activity?.toast(getString(R.string.calendar_settings_list_error))
}
}
action_show_all_day.setOnClickListener {
if (Preferences.showEvents) {
show_all_day_toggle.isChecked = !show_all_day_toggle.isChecked
}
}
show_all_day_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.calendarAllDay = isChecked
updateCalendar()
}
}
action_change_attendee_filter.setOnClickListener {
if (Preferences.showEvents) {
val selectedValues = emptyList<Int>().toMutableList()
if (Preferences.showDeclinedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
}
if (Preferences.showInvitedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
}
if (Preferences.showAcceptedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
}
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_attendee_status_title), isMultiSelection = true)
.setSelectedValues(selectedValues)
dialog.addItem(
getString(R.string.attendee_status_invited),
CalendarContract.Attendees.ATTENDEE_STATUS_INVITED
)
dialog.addItem(
getString(R.string.attendee_status_accepted),
CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED
)
dialog.addItem(
getString(R.string.attendee_status_declined),
CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED
)
dialog.addOnMultipleSelectItemListener { values ->
Preferences.showDeclinedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
Preferences.showAcceptedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
Preferences.showInvitedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
updateCalendar()
}.show()
}
}
action_show_only_busy_events.setOnClickListener {
if (Preferences.showEvents) {
show_only_busy_events_toggle.isChecked = !show_only_busy_events_toggle.isChecked
}
}
show_only_busy_events_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showOnlyBusyEvents = isChecked
updateCalendar()
}
}
action_show_multiple_events.setOnClickListener {
if (Preferences.showEvents) {
show_multiple_events_toggle.isChecked = !show_multiple_events_toggle.isChecked
}
}
show_multiple_events_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showNextEvent = isChecked
}
}
action_show_diff_time.setOnClickListener {
if (Preferences.showEvents) {
show_diff_time_toggle.isChecked = !show_diff_time_toggle.isChecked
}
}
show_diff_time_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showDiffTime = isChecked
}
}
action_widget_update_frequency.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)
.addItem(getString(R.string.settings_widget_update_frequency_high), Constants.WidgetUpdateFrequency.HIGH.value)
.addItem(getString(R.string.settings_widget_update_frequency_default), Constants.WidgetUpdateFrequency.DEFAULT.value)
.addItem(getString(R.string.settings_widget_update_frequency_low), Constants.WidgetUpdateFrequency.LOW.value)
.addOnSelectItemListener { value ->
Preferences.widgetUpdateFrequency = value
}.show()
}
}
action_second_row_info.setOnClickListener {
if (Preferences.showEvents) {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_second_row_info_title)).setSelectedValue(Preferences.secondRowInformation)
(0 .. 1).forEach {
dialog.addItem(getString(SettingsStringHelper.getSecondRowInfoString(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.secondRowInformation = value
}.show()
}
}
action_show_until.setOnClickListener {
if (Preferences.showEvents) {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_show_until_title)).setSelectedValue(Preferences.showUntil)
intArrayOf(6,7,0,1,2,3, 4, 5).forEach {
dialog.addItem(getString(SettingsStringHelper.getShowUntilString(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.showUntil = value
}.show()
}
}
action_open_event_details.setOnClickListener {
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_event_app_title)).setSelectedValue(Preferences.openEventDetails)
.addItem(getString(R.string.default_event_app), true)
.addItem(getString(R.string.default_calendar_app), false)
.addOnSelectItemListener { value ->
Preferences.openEventDetails = value
}.show()
}
}
action_calendar_app.setOnClickListener {
startActivityForResult(Intent(requireContext(), ChooseApplicationActivity::class.java), RequestCode.CALENDAR_APP_REQUEST_CODE.code)
}
}
private fun checkReadEventsPermission(showEvents: Boolean = Preferences.showEvents) {
if (activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) == true) {
show_events_label?.text = if (showEvents) getString(R.string.show_events_visible) else getString(R.string.show_events_not_visible)
read_calendar_permission_alert?.isVisible = false
} else {
show_events_label?.text = if (showEvents) getString(R.string.description_permission_calendar) else getString(R.string.show_events_not_visible)
read_calendar_permission_alert?.isVisible = showEvents
read_calendar_permission_alert?.setOnClickListener {
requirePermission()
}
}
}
private fun updateCalendar() {
if (activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) == true) {
CalendarHelper.updateEventList(requireContext())
}
}
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.READ_CALENDAR
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
checkReadEventsPermission()
} else {
Preferences.showEvents = false
}
}
}
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()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CALENDAR_APP_REQUEST_CODE.code -> {
Preferences.bulk {
calendarAppName = data?.getStringExtra(Constants.RESULT_APP_NAME) ?: getString(R.string.default_calendar_app)
calendarAppPackage = data?.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: ""
}
}
RequestCode.EVENT_APP_REQUEST_CODE.code -> {
Preferences.bulk {
eventAppName = data?.getStringExtra(Constants.RESULT_APP_NAME) ?: getString(R.string.default_event_app)
eventAppPackage = data?.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: ""
}
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
}

View File

@ -1,331 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.app.Activity
import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.FixedFocusScrollView
import com.tommasoberlose.anotherwidget.databinding.FragmentClockSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
import kotlinx.android.synthetic.main.fragment_clock_settings.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.Exception
class ClockTabFragment : Fragment() {
companion object {
fun newInstance() = ClockTabFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
private lateinit var binding: FragmentClockSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = DataBindingUtil.inflate<FragmentClockSettingsBinding>(inflater, R.layout.fragment_clock_settings, container, false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
ampm_indicator_toggle.isChecked = Preferences.showAMPMIndicator
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
setupListener()
}
private fun subscribeUi(
binding: FragmentClockSettingsBinding,
viewModel: MainViewModel
) {
binding.isClockVisible = Preferences.showClock
binding.is24Format = DateFormat.is24HourFormat(requireContext())
binding.isDarkModeEnabled = activity?.isDarkTheme() == true
viewModel.showBigClockWarning.observe(viewLifecycleOwner, Observer {
large_clock_warning?.isVisible = it
small_clock_warning?.isVisible = !it
})
viewModel.showClock.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_clock_label?.text =
if (it) getString(R.string.show_clock_visible) else getString(R.string.show_clock_not_visible)
binding.isClockVisible = it
}
})
viewModel.clockTextSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.showAMPMIndicator.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
ampm_indicator_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.clockTextColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.clockTextAlpha == "00") {
clock_text_color_label?.text = getString(R.string.transparent)
} else {
clock_text_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.clockTextColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.clockTextAlphaDark == "00") {
clock_text_color_label?.text = getString(R.string.transparent)
} else {
clock_text_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.clockTextAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.clockTextAlpha == "00") {
clock_text_color_label?.text = getString(R.string.transparent)
} else {
clock_text_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.clockTextAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.clockTextAlphaDark == "00") {
clock_text_color_label?.text = getString(R.string.transparent)
} else {
clock_text_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.clockBottomMargin.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_bottom_margin_label?.text = when (it) {
Constants.ClockBottomMargin.NONE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.ClockBottomMargin.SMALL.value -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.ClockBottomMargin.LARGE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
})
viewModel.clockAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_app_label?.text = when {
Preferences.clockAppName != "" -> Preferences.clockAppName
else -> {
if (IntentHelper.getClockIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_clock_app
)
} else {
getString(R.string.nothing)
}
}
}
}
})
}
private fun setupListener() {
action_hide_large_clock_warning.setOnClickListener {
Preferences.showBigClockWarning = false
}
action_show_clock.setOnClickListener {
Preferences.showClock = !Preferences.showClock
}
show_clock_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showClock = enabled
}
action_clock_text_size.setOnClickListener {
if (Preferences.showClock) {
val dialog = BottomSheetMenu<Float>(
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()
}
}
action_ampm_indicator_size.setOnClickListener {
if (Preferences.showClock) {
ampm_indicator_toggle.isChecked = !ampm_indicator_toggle.isChecked
}
}
ampm_indicator_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showClock) {
Preferences.showAMPMIndicator = isChecked
}
}
action_clock_text_color.setOnClickListener {
if (Preferences.showClock) {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
getSelected = { ColorHelper.getClockFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.clockTextColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.clockTextColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.clockTextAlphaDark.toIntValue() else Preferences.clockTextAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.clockTextAlphaDark = alpha.toHexValue()
} else {
Preferences.clockTextAlpha = alpha.toHexValue()
}
}
).show()
}
}
action_clock_bottom_margin_size.setOnClickListener {
if (Preferences.showClock) {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_clock_bottom_margin_title)
).setSelectedValue(Preferences.clockBottomMargin)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_none),
Constants.ClockBottomMargin.NONE.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_small),
Constants.ClockBottomMargin.SMALL.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_medium),
Constants.ClockBottomMargin.MEDIUM.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_large),
Constants.ClockBottomMargin.LARGE.value
)
.addOnSelectItemListener { value ->
Preferences.clockBottomMargin = value
}.show()
}
}
action_clock_app.setOnClickListener {
if (Preferences.showClock) {
if (Preferences.showClock) {
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java),
RequestCode.CLOCK_APP_REQUEST_CODE.code
)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == RequestCode.CLOCK_APP_REQUEST_CODE.code) {
Preferences.bulk {
clockAppName = data?.getStringExtra(Constants.RESULT_APP_NAME) ?: getString(R.string.default_clock_app)
clockAppPackage = data?.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: ""
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
binding.is24Format = DateFormat.is24HourFormat(requireContext())
super.onResume()
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
}

View File

@ -1,535 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentGeneralSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.helpers.WidgetHelper
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomDateActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomFontActivity
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.utils.isDarkTheme
import kotlinx.android.synthetic.main.fragment_clock_settings.*
import kotlinx.android.synthetic.main.fragment_general_settings.*
import kotlinx.android.synthetic.main.fragment_general_settings.scrollView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class GeneralTabFragment : Fragment() {
companion object {
fun newInstance() = GeneralTabFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentGeneralSettingsBinding>(inflater, R.layout.fragment_general_settings, container, false)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.isDarkModeEnabled = activity?.isDarkTheme() == true
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
show_dividers_toggle.isChecked = Preferences.showDividers
setupListener()
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.textMainSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
main_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textSecondSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textGlobalColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlphaDark == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlphaDark == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlpha == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlphaDark == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlpha == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlphaDark == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.secondRowTopMargin.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_row_top_margin_label?.text = when (it) {
Constants.SecondRowTopMargin.NONE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.SecondRowTopMargin.SMALL.value -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.SecondRowTopMargin.LARGE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
})
viewModel.backgroundCardColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlphaDark == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlphaDark == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textShadow.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (activity?.isDarkTheme() != true) {
text_shadow_label?.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
})
viewModel.textShadowDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (activity?.isDarkTheme() == true) {
text_shadow_label?.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
})
viewModel.dateFormat.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
date_format_label?.text = DateHelper.getDateText(requireContext(), Calendar.getInstance())
}
})
viewModel.customFont.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), it)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontFile.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontVariant.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
viewModel.showDividers.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_dividers_label?.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
}
private fun setupListener() {
action_main_text_size.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()
}
action_second_text_size.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()
}
action_font_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
getSelected = { ColorHelper.getFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.textGlobalColorDark = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textGlobalColor = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.textGlobalAlphaDark.toIntValue() else Preferences.textGlobalAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.textGlobalAlphaDark = alpha.toHexValue()
} else {
Preferences.textGlobalAlpha = alpha.toHexValue()
}
}
).show()
}
action_secondary_font_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_secondary_font_color_title),
getSelected = { ColorHelper.getSecondaryFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.textSecondaryColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textSecondaryColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.textSecondaryAlphaDark.toIntValue() else Preferences.textSecondaryAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.textSecondaryAlphaDark = alpha.toHexValue()
} else {
Preferences.textSecondaryAlpha = alpha.toHexValue()
}
}
).show()
}
action_second_row_top_margin_size.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_secondary_row_top_margin_title)
).setSelectedValue(Preferences.secondRowTopMargin)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_none),
Constants.SecondRowTopMargin.NONE.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_small),
Constants.SecondRowTopMargin.SMALL.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_medium),
Constants.SecondRowTopMargin.MEDIUM.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_large),
Constants.SecondRowTopMargin.LARGE.value
)
.addOnSelectItemListener { value ->
Preferences.secondRowTopMargin = value
}.show()
}
action_date_format.setOnClickListener {
val now = Calendar.getInstance()
val dialog = BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_date_format_title)).setSelectedValue(Preferences.dateFormat)
dialog.addItem(DateHelper.getDefaultDateText(requireContext(), now), "")
if (Preferences.dateFormat != "") {
dialog.addItem(DateHelper.getDateText(requireContext(), now), Preferences.dateFormat)
}
dialog.addItem(getString(R.string.custom_date_format), "-")
dialog.addOnSelectItemListener { value ->
when (value) {
"-" -> {
startActivity(Intent(requireContext(), CustomDateActivity::class.java))
}
"" -> {
Preferences.blockingBulk {
isDateCapitalize = false
isDateUppercase = false
}
Preferences.dateFormat = value
}
else -> {
Preferences.dateFormat = value
}
}
}.show()
}
action_background_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_background_color_title),
getSelected = { ColorHelper.getBackgroundColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.backgroundCardColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.backgroundCardColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.backgroundCardAlphaDark.toIntValue() else Preferences.backgroundCardAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.backgroundCardAlphaDark = alpha.toHexValue()
} else {
Preferences.backgroundCardAlpha = alpha.toHexValue()
}
}
).show()
}
action_text_shadow.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.title_text_shadow)).setSelectedValue(if (activity?.isDarkTheme() == true) Preferences.textShadowDark else Preferences.textShadow)
(2 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getTextShadowString(it)), it)
}
dialog.addOnSelectItemListener { value ->
if (activity?.isDarkTheme() == true) {
Preferences.textShadowDark = value
} else {
Preferences.textShadow = value
}
}.show()
}
action_custom_font.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_custom_font_title)).setSelectedValue(Preferences.customFont)
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), 0), 0)
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Constants.CUSTOM_FONT_GOOGLE_SANS), Constants.CUSTOM_FONT_GOOGLE_SANS)
}
if (Preferences.customFontFile != "") {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont), Constants.CUSTOM_FONT_DOWNLOADED)
}
dialog.addItem(getString(R.string.action_custom_font_to_search), Constants.CUSTOM_FONT_DOWNLOAD_NEW)
dialog.addOnSelectItemListener { value ->
if (value == Constants.CUSTOM_FONT_DOWNLOAD_NEW) {
startActivityForResult(
Intent(requireContext(), CustomFontActivity::class.java),
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code
)
} else if (value != Constants.CUSTOM_FONT_DOWNLOADED) {
Preferences.bulk {
customFont = value
customFontFile = ""
customFontName = ""
customFontVariant = ""
}
}
}.show()
}
action_show_dividers.setOnClickListener {
show_dividers_toggle.isChecked = !show_dividers_toggle.isChecked
}
show_dividers_toggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showDividers = isChecked
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
}

View File

@ -1,461 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.CustomNotesDialog
import com.tommasoberlose.anotherwidget.components.GlanceSettingsDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentGlanceSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver.Companion.FITNESS_OPTIONS
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import kotlinx.android.synthetic.main.fragment_glance_settings.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.idik.lib.slimadapter.SlimAdapter
class GlanceTabFragment : Fragment() {
companion object {
fun newInstance() = GlanceTabFragment()
}
private var dialog: GlanceSettingsDialog? = null
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MainViewModel
private lateinit var list: ArrayList<Constants.GlanceProviderId>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentGlanceSettingsBinding>(inflater,
R.layout.fragment_glance_settings,
container,
false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
list = GlanceProviderHelper.getGlanceProviders(requireContext())
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// List
providers_list.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(context)
providers_list.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<GlanceProvider>(R.layout.glance_provider_item) { item, injector ->
val provider = Constants.GlanceProviderId.from(item.id)!!
injector
.text(R.id.title, item.title)
.with<ImageView>(R.id.icon) {
it.setImageDrawable(ContextCompat.getDrawable(requireContext(), item.icon))
}
.clicked(R.id.item) {
if (Preferences.showGlance) {
if (provider == Constants.GlanceProviderId.CUSTOM_INFO) {
CustomNotesDialog(requireContext()){
adapter.notifyItemRangeChanged(0, adapter.data.size)
}.show()
} else {
dialog = GlanceSettingsDialog(requireActivity(), provider) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
dialog?.setOnDismissListener {
dialog = null
}
dialog?.show()
}
}
}
var isVisible = false
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
when {
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label,
if (Preferences.showMusic) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
isVisible = Preferences.showMusic
}
Preferences.showMusic -> {
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
else -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
injector.text(R.id.label,
if (Preferences.showNextAlarm && !AlarmHelper.isAlarmProbablyWrong(
requireContext())
) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon,
if (Preferences.showNextAlarm && AlarmHelper.isAlarmProbablyWrong(
requireContext())
) View.VISIBLE else View.GONE)
injector.visibility(R.id.info_icon,
if (!(Preferences.showNextAlarm && AlarmHelper.isAlarmProbablyWrong(
requireContext()))
) View.VISIBLE else View.GONE)
isVisible = !(Preferences.showNextAlarm && AlarmHelper.isAlarmProbablyWrong(
requireContext()))
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
injector.text(R.id.label,
if (Preferences.showBatteryCharging) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showBatteryCharging
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
when {
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label,
if (Preferences.showNotifications) getString(
R.string.settings_visible) else getString(R.string.settings_not_visible))
isVisible = Preferences.showNotifications
}
Preferences.showNotifications -> {
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
else -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
}
}
Constants.GlanceProviderId.GREETINGS -> {
injector.text(R.id.label,
if (Preferences.showGreetings) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showGreetings
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
injector.text(R.id.label,
if (Preferences.customNotes != "") getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.customNotes != ""
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || activity?.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION) == true)
) {
injector.text(R.id.label,
if (Preferences.showDailySteps) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showDailySteps
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.text(R.id.label, getString(R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = false
}
}
Constants.GlanceProviderId.EVENTS -> {
isVisible = Preferences.showEventsAsGlanceProvider && Preferences.showEvents && requireContext().checkGrantedPermission(Manifest.permission.READ_CALENDAR)
injector.text(R.id.label,
if (isVisible) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible))
injector.visibility(R.id.error_icon, if (isVisible) View.GONE else View.VISIBLE)
injector.visibility(R.id.info_icon, if (isVisible) View.VISIBLE else View.GONE)
}
}
injector.alpha(R.id.title, if (isVisible) 1f else .25f)
injector.alpha(R.id.label, if (isVisible) 1f else .25f)
injector.alpha(R.id.icon, if (isVisible) 1f else .25f)
}
.attachTo(providers_list)
val mIth = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
0
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,
): Boolean {
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
// move item in `fromPos` to `toPos` in adapter.
adapter.notifyItemMoved(fromPos, toPos)
return true
}
override fun onMoved(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
fromPos: Int,
target: RecyclerView.ViewHolder,
toPos: Int,
x: Int,
y: Int
) {
with(list[toPos]) {
list[toPos] = list[fromPos]
list[fromPos] = this
}
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
GlanceProviderHelper.saveGlanceProviderOrder(
list
)
adapter.updateData(list.mapNotNull { GlanceProviderHelper.getGlanceProviderById(requireContext(), it) })
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean,
) {
val view = viewHolder.itemView as MaterialCardView
if (isCurrentlyActive) {
ViewCompat.setElevation(view, 2f.convertDpToPixel(requireContext()))
view.setCardBackgroundColor(ContextCompat.getColor(requireContext(),
R.color.colorPrimary))
} else {
ViewCompat.setElevation(view, 0f)
view.setCardBackgroundColor(ContextCompat.getColor(requireContext(),
R.color.colorPrimaryDark))
}
val topEdge = if ((view.top == 0 && dY < 0) || ((view.top + view.height >= recyclerView.height - 32f.convertDpToPixel(requireContext())) && dY > 0)) 0f else dY
super.onChildDraw(c,
recyclerView,
viewHolder,
dX,
topEdge,
actionState,
isCurrentlyActive)
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int,
) {
// remove from adapter
}
})
mIth.attachToRecyclerView(providers_list)
adapter.updateData(list.mapNotNull { GlanceProviderHelper.getGlanceProviderById(requireContext(), it) })
providers_list.isNestedScrollingEnabled = false
setupListener()
}
private fun subscribeUi(
binding: FragmentGlanceSettingsBinding,
viewModel: MainViewModel,
) {
binding.isGlanceVisible = Preferences.showGlance
viewModel.showGlance.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
binding.isGlanceVisible = it
show_glance_label.text =
if (it) getString(R.string.description_show_glance_visible) else getString(
R.string.description_show_glance_not_visible)
}
})
}
private fun setupListener() {
action_show_glance.setOnClickListener {
Preferences.showGlance = !Preferences.showGlance
}
show_glance_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showGlance = enabled
}
action_show_glance.setOnLongClickListener {
Preferences.enabledGlanceProviderOrder = ""
true
}
}
private val nextAlarmChangeBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
}
override fun onStart() {
super.onStart()
activity?.registerReceiver(nextAlarmChangeBroadcastReceiver,
IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED))
if (dialog != null) {
dialog?.show()
}
}
override fun onStop() {
activity?.unregisterReceiver(nextAlarmChangeBroadcastReceiver)
super.onStop()
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
when (requestCode) {
1 -> {
if (resultCode == Activity.RESULT_OK) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
} else {
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}
}
2 -> {
try {
val account: GoogleSignInAccount? = GoogleSignIn.getSignedInAccountFromIntent(
data).getResult(ApiException::class.java)
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
GoogleSignIn.requestPermissions(
requireActivity(),
1,
account,
FITNESS_OPTIONS)
} else {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
} catch (e: ApiException) {
e.printStackTrace()
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}
}
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
override fun onResume() {
super.onResume()
adapter.notifyItemRangeChanged(0, adapter.data.size)
if (dialog != null) {
dialog?.show()
}
}
}

View File

@ -1,64 +1,62 @@
package com.tommasoberlose.anotherwidget.ui.fragments package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.animation.addListener
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation import androidx.navigation.Navigation
import com.google.android.material.badge.BadgeDrawable import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentAppMainBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.* import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.adapters.ViewPagerAdapter
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.ui.widgets.StandardWidget
import com.tommasoberlose.anotherwidget.utils.* import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.android.synthetic.main.fragment_app_main.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener { class MainFragment : Fragment() {
companion object { companion object {
fun newInstance() = MainFragment() fun newInstance() = MainFragment()
private const val PREVIEW_BASE_HEIGHT = 120 private val PREVIEW_BASE_HEIGHT: Int
get() = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) 120 else 180
} }
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentAppMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
} }
override fun onCreateView( override fun onCreateView(
@ -66,41 +64,7 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java) viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
return inflater.inflate(R.layout.fragment_app_main, container, false) binding = FragmentAppMainBinding.inflate(inflater)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Viewpager
pager.adapter = ViewPagerAdapter(requireActivity())
pager.offscreenPageLimit = 4
TabLayoutMediator(tabs, pager) { tab, position ->
tab.text = when (position) {
0 -> getString(R.string.settings_general_title)
1 -> getString(R.string.settings_calendar_title)
2 -> getString(R.string.settings_weather_title)
3 -> getString(R.string.settings_clock_title)
4 -> getString(R.string.settings_at_a_glance_title)
else -> ""
}
}.attach()
// Init clock
if (Preferences.showClock) {
time.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time.setTextSize(TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()))
time_am_pm.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm.setTextSize(TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2)
}
time_container.isVisible = Preferences.showClock
preview.layoutParams = preview.layoutParams.apply {
height = PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(requireContext()) else 0
}
subscribeUi(viewModel)
// Warnings // Warnings
if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) { if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) {
@ -117,293 +81,327 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
} }
.show() .show()
} }
}
private var uiJob: Job? = null val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
navHost?.navController?.addOnDestinationChangedListener { controller, destination, _ ->
private fun updateUI() { val show = destination.id != R.id.tabSelectorFragment
uiJob?.cancel() binding.actionBack.animate().alpha(if (show) 1f else 0f).setDuration(200).translationX((if (show) 0f else 4f).convertDpToPixel(requireContext())).start()
binding.actionBack.setOnSingleClickListener {
preview?.clearAnimation() controller.navigateUp()
time_container?.clearAnimation()
if (Preferences.showPreview) {
preview?.setCardBackgroundColor(
ContextCompat.getColor(
requireContext(),
if (ColorHelper.getFontColor(activity?.isDarkTheme() == true)
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
)
widget_shape_background?.setImageDrawable(
BitmapHelper.getTintedDrawable(
requireContext(),
R.drawable.card_background,
ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true)
)
)
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.generateWidgetView(requireContext(), typeface)
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview?.measure(0, 0)
}
val bitmap = if (preview != null) {
BitmapHelper.getBitmapFromView(
generatedView,
if (preview.width > 0) preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
} else {
null
}
withContext(Dispatchers.Main) {
// Clock
time?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
time_am_pm?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
time_am_pm?.isVisible = Preferences.showAMPMIndicator
// Clock bottom margin
clock_bottom_margin_none?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.value
clock_bottom_margin_small?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value
clock_bottom_margin_medium?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value
clock_bottom_margin_large?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value
if ((Preferences.showClock && (time?.alpha ?: 1f < 1f)) || (!Preferences.showClock && (time?.alpha ?: 0f > 0f))) {
if (Preferences.showClock) {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
val initialHeight = time_container?.measuredHeight ?: 0
ValueAnimator.ofFloat(
if (Preferences.showClock) 0f else 1f,
if (Preferences.showClock) 1f else 0f
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
time_container?.layoutParams =
time_container.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
time?.alpha = animatedValue
}
addListener(
onStart = {
if (Preferences.showClock) {
time_container?.isVisible = true
}
},
onEnd = {
if (!Preferences.showClock) {
time_container?.isVisible = false
}
}
)
}.start()
if (preview != null) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 500L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}
}.start()
}
} else {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
if (preview != null && preview.height == 0) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview?.layoutParams = layoutParams
}
}
}.start()
}
widget_loader?.animate()?.scaleX(0f)?.scaleY(0f)?.alpha(0f)
?.setDuration(200L)?.start()
bitmap_container?.apply {
setImageBitmap(bitmap)
scaleX = 0.9f
scaleY = 0.9f
}
widget?.animate()?.alpha(1f)?.start()
}
}
}
} else {
if (preview != null) {
ValueAnimator.ofInt(
preview.height,
0
).apply {
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}
}.start()
} }
binding.actionBack.isClickable = show
binding.actionBack.isFocusable = show
binding.actionSettings.animate().alpha(if (!show) 1f else 0f).setDuration(200).translationX((if (!show) 0f else -4f).convertDpToPixel(requireContext())).start()
binding.actionSettings.isClickable = !show
binding.actionSettings.isFocusable = !show
binding.fragmentTitle.text = if (show) destination.label.toString() else getString(R.string.app_name)
} }
showErrorBadge() 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
}
subscribeUi(viewModel)
return binding.root
} }
private fun subscribeUi(viewModel: MainViewModel) { private fun subscribeUi(viewModel: MainViewModel) {
viewModel.showWallpaper.observe(viewLifecycleOwner, Observer { viewModel.showWallpaper.observe(viewLifecycleOwner) {
activity?.let { act -> if (it) {
val wallpaper = act.getCurrentWallpaper() val wallpaper = requireActivity().getCurrentWallpaper()
widget_bg.setImageDrawable(if (it) wallpaper else null) binding.widgetBg.setImageDrawable(if (it) wallpaper else null)
if (wallpaper != null) { if (wallpaper != null) {
widget_bg.layoutParams = binding.widgetBg.layoutParams =
(widget_bg.layoutParams as ViewGroup.MarginLayoutParams).apply { (binding.widgetBg.layoutParams as ViewGroup.MarginLayoutParams).apply {
val metrics = DisplayMetrics() val metrics = DisplayMetrics()
act.windowManager.defaultDisplay.getMetrics(metrics)
val dimensions: Pair<Int, Int> = if (wallpaper.intrinsicWidth >= wallpaper.intrinsicHeight) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
metrics.heightPixels to (wallpaper.intrinsicWidth) * metrics.heightPixels / (wallpaper.intrinsicHeight) val display = requireActivity().display
display?.getRealMetrics(metrics)
} else { } else {
metrics.widthPixels to (wallpaper.intrinsicHeight) * metrics.widthPixels / (wallpaper.intrinsicWidth) @Suppress("DEPRECATION")
val display = requireActivity().windowManager.defaultDisplay
@Suppress("DEPRECATION")
display.getMetrics(metrics)
} }
setMargins( val dimensions: Pair<Int, Int> =
if (dimensions.first >= dimensions.second) (-80).toPixel(requireContext()) else 0, if (wallpaper.intrinsicWidth >= wallpaper.intrinsicHeight) {
(-80).toPixel(requireContext()), 0, 0 metrics.heightPixels to (wallpaper.intrinsicWidth) * metrics.heightPixels / (wallpaper.intrinsicHeight)
} else {
metrics.widthPixels to (wallpaper.intrinsicHeight) * metrics.widthPixels / (wallpaper.intrinsicWidth)
}
setMargins(0, (-80).toPixel(requireContext()), 0, 0
) )
width = dimensions.first width = dimensions.first
height = dimensions.second height = dimensions.second
} }
} }
} else {
binding.widgetBg.setImageDrawable(null)
} }
})
logo.setOnClickListener {
// startActivity(Intent(this, SupportDevActivity::class.java))
} }
action_settings.setOnClickListener { viewModel.fragmentScrollY.observe(viewLifecycleOwner) {
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment) 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 fun showErrorBadge() { private var uiJob: Job? = null
// Calendar error indicator
tabs?.getTabAt(1)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showEvents && activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) != true
// Weather error indicator private fun updateUI() {
tabs?.getTabAt(2)?.orCreateBadge?.apply { if (Preferences.showPreview) {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText) lifecycleScope.launch(Dispatchers.IO) {
badgeGravity = BadgeDrawable.TOP_END val bgColor: Int = ContextCompat.getColor(
}?.isVisible = if (Preferences.showWeather) { requireContext(),
(WeatherHelper.isKeyRequired() && WeatherHelper.getApiKey() == "") if (ColorHelper.getFontColor(requireActivity().isDarkTheme())
|| (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission( .isColorDark()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION ) android.R.color.white else R.color.colorAccent
) != true) )
|| (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-")
|| (Preferences.weatherProviderLocationError != "") 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
if (generatedView != null) {
withContext(Dispatchers.Main) {
binding.widgetDetail.content.removeAllViews()
val container = LinearLayout(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
container.gravity = when (Preferences.widgetAlign) {
Constants.WidgetAlign.CENTER.rawValue -> Gravity.CENTER_HORIZONTAL
Constants.WidgetAlign.LEFT.rawValue -> Gravity.START
Constants.WidgetAlign.RIGHT.rawValue -> Gravity.END
else -> Gravity.NO_GRAVITY
}
container.addView(generatedView)
binding.widgetDetail.content.addView(container)
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
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 { } else {
false binding.widgetDetail.timezonesContainer.isVisible = false
} }
// Glance error indicator // Clock bottom margin
tabs?.getTabAt(4)?.orCreateBadge?.apply { binding.widgetDetail.clockBottomMarginNone.isVisible =
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText) Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.rawValue
badgeGravity = BadgeDrawable.TOP_END binding.widgetDetail.clockBottomMarginSmall.isVisible =
}?.isVisible = ((Preferences.showMusic || Preferences.showNotifications) && !ActiveNotificationsHelper.checkNotificationAccess(requireContext())) || Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.rawValue
(Preferences.showDailySteps && !(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION))) || binding.widgetDetail.clockBottomMarginMedium.isVisible =
(AlarmHelper.isAlarmProbablyWrong(requireContext())) || Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.rawValue
(Preferences.showEventsAsGlanceProvider && (!Preferences.showEvents || !requireContext().checkGrantedPermission(Manifest.permission.READ_CALENDAR))) 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 = when (Preferences.widgetAlign) {
Constants.WidgetAlign.CENTER.rawValue -> Gravity.CENTER_HORIZONTAL
Constants.WidgetAlign.LEFT.rawValue -> Gravity.START
Constants.WidgetAlign.RIGHT.rawValue -> Gravity.END
else -> Gravity.NO_GRAVITY
}
}
if (Preferences.widgetAlign == Constants.WidgetAlign.RIGHT.rawValue) {
with (binding.widgetDetail.timeContainer) {
val child = getChildAt(2)
if (child.id == R.id.timezones_container) {
removeViewAt(2)
child.layoutParams = (child.layoutParams as ViewGroup.MarginLayoutParams).apply {
marginEnd = 16f.convertDpToPixel(requireContext()).toInt()
}
addView(child, 0)
}
}
} else {
with (binding.widgetDetail.timeContainer) {
val child = getChildAt(0)
if (child.id == R.id.timezones_container) {
removeViewAt(0)
child.layoutParams = (child.layoutParams as ViewGroup.MarginLayoutParams).apply {
marginEnd = 0
}
addView(child, 2)
}
}
}
}
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 = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
binding.widgetDetail.timeContainer.layoutParams =
binding.widgetDetail.timeContainer.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
binding.widgetDetail.time.alpha = animatedValue
binding.widgetDetail.timeAmPm.alpha = animatedValue
binding.widgetDetail.altTimezoneTime.alpha = animatedValue
binding.widgetDetail.altTimezoneTimeAmPm.alpha = animatedValue
binding.widgetDetail.altTimezoneLabel.alpha = animatedValue
}
}.start()
}
}
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)) {
ValueAnimator.ofInt(
binding.preview.height,
(if (Preferences.showPreview) PREVIEW_BASE_HEIGHT.toPixel(requireContext()) else 0) + (if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0)
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams
layoutParams.height = animatedValue
binding.preview.layoutParams = layoutParams
}
}.start()
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Preferences.preferences.registerOnSharedPreferenceChangeListener(this)
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
showErrorBadge()
updateUI() updateUI()
} }
override fun onPause() { override fun onPause() {
Preferences.preferences.unregisterOnSharedPreferenceChangeListener(this)
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
super.onPause() super.onPause()
} }
private var delayJob: Job? = null private var delayJob: Job? = null
override fun onSharedPreferenceChanged(preferences: SharedPreferences, p1: String) {
delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
withContext(Dispatchers.Main) {
updateUI()
}
}
MainWidget.updateWidget(requireContext())
}
class UpdateUiMessageEvent class UpdateUiMessageEvent
class ChangeTabEvent(val page: Int) class ChangeTabEvent(val page: Int)
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: UpdateUiMessageEvent?) { fun onUpdateUiEvent(ignore: UpdateUiMessageEvent?) {
delayJob?.cancel() delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) { delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200) delay(300)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
updateUI() updateUI()
} }
@ -411,9 +409,8 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: ChangeTabEvent?) { fun onChangeTabEvent(ignore: ChangeTabEvent) {
event?.let { val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
pager.setCurrentItem(event.page, true) navHost?.navController?.navigateUp()
}
} }
} }

View File

@ -4,7 +4,6 @@ import android.Manifest
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -15,6 +14,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.transition.TransitionInflater
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.MultiplePermissionsReport
@ -24,19 +24,21 @@ import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.BuildConfig import com.tommasoberlose.anotherwidget.BuildConfig
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentSettingsBinding import com.tommasoberlose.anotherwidget.databinding.FragmentAppSettingsBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.SupportDevActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.activities.IntegrationsActivity import com.tommasoberlose.anotherwidget.ui.activities.settings.IntegrationsActivity
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.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import com.tommasoberlose.anotherwidget.utils.openURI import com.tommasoberlose.anotherwidget.utils.openURI
import kotlinx.android.synthetic.main.fragment_settings.* import com.tommasoberlose.anotherwidget.utils.setOnSingleClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -48,20 +50,22 @@ class SettingsFragment : Fragment() {
} }
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentAppSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) // sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?,
): View { ): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java) viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentSettingsBinding>(inflater, R.layout.fragment_settings, container, false) binding = FragmentAppSettingsBinding.inflate(inflater)
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.viewModel = viewModel binding.viewModel = viewModel
@ -74,25 +78,29 @@ class SettingsFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
action_back.setOnClickListener { binding.actionBack.setOnSingleClickListener {
Navigation.findNavController(it).popBackStack() Navigation.findNavController(it).popBackStack()
} }
show_widget_preview_toggle.isChecked = Preferences.showPreview binding.showWidgetPreviewToggle.setCheckedImmediatelyNoEvent(Preferences.showPreview)
show_wallpaper_toggle.isChecked = Preferences.showWallpaper binding.showWallpaperToggle.setCheckedImmediatelyNoEvent(Preferences.showWallpaper)
setupListener() setupListener()
app_version.text = "v%s (%s)".format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) binding.appVersion.text = "v%s (%s)".format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
binding.toolbar.cardElevation = if (binding.scrollView.scrollY > 0) 32f else 0f
}
} }
private fun subscribeUi( private fun subscribeUi(
viewModel: MainViewModel viewModel: MainViewModel,
) { ) {
viewModel.darkThemePreference.observe(viewLifecycleOwner, Observer { viewModel.darkThemePreference.observe(viewLifecycleOwner) {
AppCompatDelegate.setDefaultNightMode(it) AppCompatDelegate.setDefaultNightMode(it)
maintainScrollPosition { maintainScrollPosition {
theme?.text = when (it) { binding.theme.text = when (it) {
AppCompatDelegate.MODE_NIGHT_NO -> getString(R.string.settings_subtitle_dark_theme_light) AppCompatDelegate.MODE_NIGHT_NO -> getString(R.string.settings_subtitle_dark_theme_light)
AppCompatDelegate.MODE_NIGHT_YES -> getString(R.string.settings_subtitle_dark_theme_dark) AppCompatDelegate.MODE_NIGHT_YES -> getString(R.string.settings_subtitle_dark_theme_dark)
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY -> getString(R.string.settings_subtitle_dark_theme_by_battery_saver) AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY -> getString(R.string.settings_subtitle_dark_theme_by_battery_saver)
@ -100,43 +108,45 @@ class SettingsFragment : Fragment() {
else -> "" else -> ""
} }
} }
}) }
viewModel.installedIntegrations.observe(viewLifecycleOwner, Observer { viewModel.installedIntegrations.observe(viewLifecycleOwner) {
integrations_count_label?.text = getString(R.string.label_count_installed_integrations).format(it) binding.integrationsCountLabel.text =
}) getString(R.string.label_count_installed_integrations).format(
it)
}
viewModel.showPreview.observe(viewLifecycleOwner, Observer { viewModel.showPreview.observe(viewLifecycleOwner) {
maintainScrollPosition { maintainScrollPosition {
show_widget_preview_label?.text = binding.showWidgetPreviewLabel.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible) if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
} }
}) }
viewModel.showWallpaper.observe(viewLifecycleOwner, Observer { viewModel.showWallpaper.observe(viewLifecycleOwner) {
maintainScrollPosition { maintainScrollPosition {
show_wallpaper_label?.text = binding.showWallpaperLabel.text =
if (it && activity?.checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == true) getString( if (it && requireActivity().checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) getString(
R.string.settings_visible R.string.settings_visible
) else getString(R.string.settings_not_visible) ) else getString(R.string.settings_not_visible)
} }
}) }
} }
private fun setupListener() { private fun setupListener() {
action_show_widget_preview.setOnClickListener { binding.actionShowWidgetPreview.setOnClickListener {
show_widget_preview_toggle.isChecked = !show_widget_preview_toggle.isChecked binding.showWidgetPreviewToggle.isChecked = !binding.showWidgetPreviewToggle.isChecked
} }
show_widget_preview_toggle.setOnCheckedChangeListener { _, isChecked -> binding.showWidgetPreviewToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showPreview = isChecked Preferences.showPreview = isChecked
} }
action_show_wallpaper.setOnClickListener { binding.actionShowWallpaper.setOnClickListener {
show_wallpaper_toggle.isChecked = !show_wallpaper_toggle.isChecked binding.showWallpaperToggle.isChecked = !binding.showWallpaperToggle.isChecked
} }
show_wallpaper_toggle.setOnCheckedChangeListener { _, isChecked -> binding.showWallpaperToggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
requirePermission() requirePermission()
} else { } else {
@ -144,13 +154,14 @@ class SettingsFragment : Fragment() {
} }
} }
action_integrations.setOnClickListener { binding.actionIntegrations.setOnClickListener {
startActivity(Intent(requireContext(), IntegrationsActivity::class.java)) startActivity(Intent(requireContext(), IntegrationsActivity::class.java))
} }
action_change_theme.setOnClickListener { binding.actionChangeTheme.setOnClickListener {
maintainScrollPosition { maintainScrollPosition {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_theme_title)) BottomSheetMenu<Int>(requireContext(),
header = getString(R.string.settings_theme_title))
.setSelectedValue(Preferences.darkThemePreference) .setSelectedValue(Preferences.darkThemePreference)
.addItem( .addItem(
getString(R.string.settings_subtitle_dark_theme_light), getString(R.string.settings_subtitle_dark_theme_light),
@ -170,62 +181,78 @@ class SettingsFragment : Fragment() {
} }
} }
action_translate.setOnClickListener { binding.actionTranslate.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml") requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml")
} }
action_website.setOnClickListener { binding.actionWebsite.setOnClickListener {
activity?.openURI("http://tommasoberlose.com/") requireActivity().openURI("http://tommasoberlose.com/")
} }
action_feedback.setOnClickListener { binding.actionFeedback.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/issues") requireActivity().openURI("https://github.com/tommasoberlose/another-widget/issues")
} }
action_privacy_policy.setOnClickListener { binding.actionPrivacyPolicy.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md") requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md")
} }
action_help_dev.setOnClickListener { binding.actionHelpDev.setOnClickListener {
startActivity(Intent(requireContext(), SupportDevActivity::class.java)) startActivity(Intent(requireContext(), SupportDevActivity::class.java))
} }
action_refresh_widget.setOnClickListener { binding.actionRefreshWidget.setOnClickListener {
viewLifecycleOwner.lifecycleScope.launch { binding.actionRefreshIcon
WeatherHelper.updateWeather(requireContext()) .animate()
} .rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
CalendarHelper.updateEventList(requireContext()) .withEndAction {
MediaPlayerHelper.updatePlayingMediaInfo(requireContext()) viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
ActiveNotificationsHelper.clearLastNotification(requireContext()) try {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
.start()
} }
} }
private fun maintainScrollPosition(callback: () -> Unit) { private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false binding.scrollView.isScrollable = false
callback.invoke() callback.invoke()
lifecycleScope.launch { lifecycleScope.launch {
delay(200) delay(200)
scrollView.isScrollable = true binding.scrollView.isScrollable = true
} }
} }
override fun onResume() {
super.onResume()
binding.showWallpaperToggle.setCheckedNoEvent(Preferences.showWallpaper && requireActivity().checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE))
}
private fun requirePermission() { private fun requirePermission() {
Dexter.withContext(requireContext()) Dexter.withContext(requireContext())
.withPermissions( .withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
).withListener(object: MultiplePermissionsListener { ).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let { report?.let {
if (report.areAllPermissionsGranted()) { if (report.areAllPermissionsGranted()) {
Preferences.showWallpaper = true Preferences.showWallpaper = true
} else { } else {
show_wallpaper_toggle?.isChecked = false binding.showWallpaperToggle.setCheckedNoEvent(false)
} }
} }
} }
override fun onPermissionRationaleShouldBeShown( override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?, permissions: MutableList<PermissionRequest>?,
token: PermissionToken? token: PermissionToken?,
) { ) {
// Remember to invoke this method when the custom rationale is closed // 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. // or just by default if you don't want to use any custom rationale.

View File

@ -1,331 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.os.BuildCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.bulk
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.IconPackSelector
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentWeatherSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomLocationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.WeatherProviderActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.collapse
import com.tommasoberlose.anotherwidget.utils.expand
import kotlinx.android.synthetic.main.fragment_weather_settings.*
import kotlinx.android.synthetic.main.fragment_weather_settings.scrollView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class WeatherTabFragment : Fragment() {
companion object {
fun newInstance() = WeatherTabFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentWeatherSettingsBinding>(inflater, R.layout.fragment_weather_settings, container, false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
}
private fun subscribeUi(
binding: FragmentWeatherSettingsBinding,
viewModel: MainViewModel
) {
binding.isWeatherVisible = Preferences.showWeather
viewModel.showWeather.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_weather_label?.text =
if (it) getString(R.string.show_weather_visible) else getString(R.string.show_weather_not_visible)
checkWeatherProviderConfig()
binding.isWeatherVisible = it
}
checkLocationPermission()
checkWeatherProviderConfig()
})
viewModel.weatherProvider.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_provider.text = WeatherHelper.getProviderName(requireContext(), Constants.WeatherProvider.fromInt(it)!!)
checkWeatherProviderConfig()
}
})
viewModel.weatherProviderError.observe(viewLifecycleOwner, Observer {
checkWeatherProviderConfig()
})
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner, Observer {
checkWeatherProviderConfig()
})
viewModel.customLocationAdd.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
background_location_warning.isVisible = it == ""
label_custom_location?.text =
if (it == "") getString(R.string.custom_location_gps) else it
}
checkLocationPermission()
})
viewModel.weatherTempUnit.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
temp_unit?.text =
if (it == "F") getString(R.string.fahrenheit) else getString(R.string.celsius)
}
checkLocationPermission()
})
viewModel.weatherRefreshPeriod.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_refresh_period?.text = getString(SettingsStringHelper.getRefreshPeriodString(it))
}
checkLocationPermission()
})
viewModel.weatherIconPack.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_icon_pack?.text = getString(R.string.settings_weather_icon_pack_default).format((it + 1))
// weather_icon_pack.setImageDrawable(ContextCompat.getDrawable(requireContext(), WeatherHelper.getWeatherIconResource("02d")))
// if (it == Constants.WeatherIconPack.MINIMAL.value) {
// weather_icon_pack.setColorFilter(ContextCompat.getColor(requireContext(), R.color.colorPrimaryText))
// } else {
// weather_icon_pack.setColorFilter(ContextCompat.getColor(requireContext(), android.R.color.transparent))
// }
}
checkLocationPermission()
})
viewModel.weatherAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
weather_app_label?.text =
if (it != "") it else getString(R.string.default_weather_app)
}
})
}
private fun checkLocationPermission() {
// Background permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && activity?.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) == true && activity?.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != true) {
requirePermission()
}
if (activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) == true) {
location_permission_alert?.isVisible = false
background_location_warning.isVisible = Preferences.customLocationAdd == ""
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
location_permission_alert?.isVisible = true
background_location_warning.isVisible = true
location_permission_alert?.setOnClickListener {
MaterialBottomSheetDialog(requireContext(), message = getString(R.string.background_location_warning))
.setPositiveButton(getString(android.R.string.ok)) {
requirePermission()
}
.show()
}
} else {
location_permission_alert?.isVisible = false
}
}
private fun checkWeatherProviderConfig() {
if (Preferences.showWeather && Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-" && !location_permission_alert.isVisible) {
weather_provider_error.expand()
} else {
weather_provider_error.collapse()
}
weather_provider_error?.text = Preferences.weatherProviderError
if (Preferences.showWeather && Preferences.weatherProviderLocationError != "" && !location_permission_alert.isVisible) {
weather_provider_location_error.expand()
} else {
weather_provider_location_error.collapse()
}
weather_provider_location_error?.text = Preferences.weatherProviderLocationError
}
private fun setupListener() {
action_show_weather.setOnClickListener {
Preferences.showWeather = !Preferences.showWeather
}
show_weather_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showWeather = enabled
}
action_weather_provider.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
Intent(requireContext(), WeatherProviderActivity::class.java),
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code
)
}
}
action_custom_location.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
Intent(requireContext(), CustomLocationActivity::class.java),
Constants.RESULT_CODE_CUSTOM_LOCATION
)
}
}
action_change_unit.setOnClickListener {
if (Preferences.showWeather) {
BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_unit_title)).setSelectedValue(Preferences.weatherTempUnit)
.addItem(getString(R.string.fahrenheit), "F")
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
Preferences.weatherTempUnit = value
}.show()
}
}
action_weather_refresh_period.setOnClickListener {
if (Preferences.showWeather) {
val dialog =
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_weather_refresh_period_title)).setSelectedValue(Preferences.weatherRefreshPeriod)
(5 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getRefreshPeriodString(it)), it)
}
dialog
.addOnSelectItemListener { value ->
Preferences.weatherRefreshPeriod = value
}.show()
}
}
action_weather_icon_pack.setOnClickListener {
if (Preferences.showWeather) {
IconPackSelector(requireContext(), header = getString(R.string.settings_weather_icon_pack_title)).show()
}
}
action_weather_app.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java),
RequestCode.WEATHER_APP_REQUEST_CODE.code
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
WeatherReceiver.setUpdates(requireContext())
checkLocationPermission()
}
RequestCode.WEATHER_APP_REQUEST_CODE.code -> {
Preferences.bulk {
weatherAppName = data?.getStringExtra(Constants.RESULT_APP_NAME) ?: getString(R.string.default_weather_app)
weatherAppPackage = data?.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: ""
}
MainWidget.updateWidget(requireContext())
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
checkLocationPermission()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
checkLocationPermission()
}
}
}
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 maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,297 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.Manifest
import android.os.Bundle
import android.provider.CalendarContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.models.CalendarSelector
import com.tommasoberlose.anotherwidget.databinding.FragmentTabCalendarBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class CalendarFragment : Fragment() {
companion object {
fun newInstance() = CalendarFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentTabCalendarBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabCalendarBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.showAllDayToggle.setCheckedImmediatelyNoEvent(Preferences.calendarAllDay)
binding.showOnlyBusyEventsToggle.setCheckedImmediatelyNoEvent(Preferences.showOnlyBusyEvents)
binding.showDiffTimeToggle.setCheckedImmediatelyNoEvent(Preferences.showDiffTime)
binding.showNextEventOnMultipleLinesToggle.setCheckedImmediatelyNoEvent(Preferences.showNextEventOnMultipleLines)
setupListener()
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
private fun subscribeUi(
viewModel: MainViewModel
) {
binding.isCalendarEnabled = Preferences.showEvents
binding.isDiffEnabled = Preferences.showDiffTime
viewModel.calendarAllDay.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.allDayLabel.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
}
viewModel.secondRowInformation.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.secondRowInfoLabel.text = getString(SettingsStringHelper.getSecondRowInfoString(it))
}
}
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)
binding.isDiffEnabled = it || !Preferences.showEvents
}
}
viewModel.widgetUpdateFrequency.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.widgetUpdateFrequencyLabel.text = when (it) {
Constants.WidgetUpdateFrequency.HIGH.rawValue -> getString(R.string.settings_widget_update_frequency_high)
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> getString(R.string.settings_widget_update_frequency_default)
Constants.WidgetUpdateFrequency.LOW.rawValue -> getString(R.string.settings_widget_update_frequency_low)
else -> ""
}
}
}
viewModel.showUntil.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showUntilLabel.text = getString(SettingsStringHelper.getShowUntilString(it))
}
}
}
private fun setupListener() {
binding.actionFilterCalendar.setOnClickListener {
val calendarSelectorList: List<CalendarSelector> = CalendarHelper.getCalendarList(requireContext()).map {
CalendarSelector(
it.id,
it.displayName,
it.accountName
)
}.sortedWith { cal1, cal2 ->
when {
cal1.accountName != cal2.accountName -> {
cal1.accountName.compareTo(cal2.accountName)
}
cal1.accountName == cal1.name -> {
-1
}
cal2.accountName == cal2.name -> {
1
}
else -> {
cal1.name.compareTo(cal2.name)
}
}
}
if (calendarSelectorList.isNotEmpty()) {
val filteredCalendarIds = CalendarHelper.getFilteredCalendarIdList()
val visibleCalendarIds = calendarSelectorList.map { it.id }.filter { id: Long -> !filteredCalendarIds.contains(id) }
val dialog = BottomSheetMenu<Long>(requireContext(), header = getString(R.string.settings_filter_calendar_subtitle), isMultiSelection = true)
.setSelectedValues(visibleCalendarIds)
calendarSelectorList.indices.forEach { index ->
if (index == 0 || calendarSelectorList[index].accountName != calendarSelectorList[index - 1].accountName) {
dialog.addItem(calendarSelectorList[index].accountName)
}
dialog.addItem(
if (calendarSelectorList[index].name == calendarSelectorList[index].accountName) getString(R.string.main_calendar) else calendarSelectorList[index].name,
calendarSelectorList[index].id
)
}
dialog.addOnMultipleSelectItemListener { values ->
CalendarHelper.filterCalendar(calendarSelectorList.map { it.id }.filter { !values.contains(it) })
updateCalendar()
}.show()
} else {
requireActivity().toast(getString(R.string.calendar_settings_list_error))
}
}
binding.actionShowAllDay.setOnClickListener {
binding.showAllDayToggle.isChecked = !binding.showAllDayToggle.isChecked
}
binding.showAllDayToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.calendarAllDay = isChecked
MainWidget.updateWidget(requireContext())
}
binding.actionChangeAttendeeFilter.setOnClickListener {
val selectedValues = emptyList<Int>().toMutableList()
if (Preferences.showDeclinedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
}
if (Preferences.showInvitedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
}
if (Preferences.showAcceptedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
}
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_attendee_status_title), isMultiSelection = true)
.setSelectedValues(selectedValues)
dialog.addItem(
getString(R.string.attendee_status_invited),
CalendarContract.Attendees.ATTENDEE_STATUS_INVITED
)
dialog.addItem(
getString(R.string.attendee_status_accepted),
CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED
)
dialog.addItem(
getString(R.string.attendee_status_declined),
CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED
)
dialog.addOnMultipleSelectItemListener { values ->
Preferences.showDeclinedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
Preferences.showAcceptedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
Preferences.showInvitedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
updateCalendar()
}.show()
}
binding.actionShowOnlyBusyEvents.setOnClickListener {
binding.showOnlyBusyEventsToggle.isChecked = !binding.showOnlyBusyEventsToggle.isChecked
}
binding.showOnlyBusyEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showOnlyBusyEvents = isChecked
MainWidget.updateWidget(requireContext())
}
binding.actionShowDiffTime.setOnClickListener {
binding.showDiffTimeToggle.isChecked = !binding.showDiffTimeToggle.isChecked
}
binding.showDiffTimeToggle.setOnCheckedChangeListener { _, isChecked ->
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)
.addItem(getString(R.string.settings_widget_update_frequency_high), Constants.WidgetUpdateFrequency.HIGH.rawValue)
.addItem(getString(R.string.settings_widget_update_frequency_default), Constants.WidgetUpdateFrequency.DEFAULT.rawValue)
.addItem(getString(R.string.settings_widget_update_frequency_low), Constants.WidgetUpdateFrequency.LOW.rawValue)
.addOnSelectItemListener { value ->
Preferences.widgetUpdateFrequency = value
}.show()
}
}
binding.actionSecondRowInfo.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_second_row_info_title)).setSelectedValue(Preferences.secondRowInformation)
(0 .. 1).forEach {
dialog.addItem(getString(SettingsStringHelper.getSecondRowInfoString(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.secondRowInformation = value
}.show()
}
binding.actionShowUntil.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_show_until_title)).setSelectedValue(Preferences.showUntil)
intArrayOf(6,7,0,1,2,3, 4, 5).forEach {
dialog.addItem(getString(SettingsStringHelper.getShowUntilString(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.showUntil = value
updateCalendar()
}.show()
}
}
private fun updateCalendar() {
if (requireActivity().checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
CalendarHelper.updateEventList(requireContext())
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,212 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
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.databinding.FragmentTabClockBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.ui.activities.tabs.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.TimeZoneSelectorActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ClockFragment : Fragment() {
companion object {
fun newInstance() = ClockFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
private lateinit var binding: FragmentTabClockBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabClockBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.ampmIndicatorToggle.setCheckedImmediatelyNoEvent(Preferences.showAMPMIndicator)
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
setupListener()
binding.scrollView.viewTreeObserver?.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
private fun subscribeUi(
viewModel: MainViewModel
) {
binding.isClockVisible = Preferences.showClock
binding.is24Format = DateFormat.is24HourFormat(requireContext())
binding.isDarkModeEnabled = activity?.isDarkTheme() == true
viewModel.clockTextSize.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.clockTextSizeLabel.text = String.format("%.0fsp", it)
}
}
viewModel.altTimezoneLabel.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (it != "") {
binding.altTimezoneClockLabel.text =
String.format("%s (%s)", it, Preferences.altTimezoneId)
} else {
binding.altTimezoneClockLabel.text = getString(R.string.no_time_zone_label)
}
}
}
viewModel.showAMPMIndicator.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.ampmIndicatorLabel.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
}
viewModel.clockTextColor.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (Preferences.clockTextAlpha == "00") {
binding.clockTextColorLabel.text = getString(R.string.transparent)
} else {
binding.clockTextColorLabel.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
}
viewModel.clockTextColorDark.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (Preferences.clockTextAlphaDark == "00") {
binding.clockTextColorLabel.text = getString(R.string.transparent)
} else {
binding.clockTextColorLabel.text =
"#%s".format(Integer.toHexString(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
}
}
private fun setupListener() {
binding.actionClockTextSize.setOnClickListener {
val dialog = BottomSheetMenu<Float>(
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()
}
binding.actionAltTimezoneClock.setOnClickListener {
startActivity(Intent(requireContext(), TimeZoneSelectorActivity::class.java))
}
binding.actionAmpmIndicatorSize.setOnClickListener {
binding.ampmIndicatorToggle.isChecked = !binding.ampmIndicatorToggle.isChecked
}
binding.ampmIndicatorToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showAMPMIndicator = isChecked
}
binding.actionClockTextColor.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
getSelected = { ColorHelper.getClockFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.clockTextColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.clockTextColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.clockTextAlphaDark.toIntValue() else Preferences.clockTextAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.clockTextAlphaDark = alpha.toHexValue()
} else {
Preferences.clockTextAlpha = alpha.toHexValue()
}
}
).show()
}
}
override fun onResume() {
binding.is24Format = DateFormat.is24HourFormat(requireContext())
super.onResume()
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,241 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.blockingBulk
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.databinding.FragmentTabGesturesBinding
import com.tommasoberlose.anotherwidget.databinding.FragmentTabLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.ui.activities.tabs.CustomDateActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class GesturesFragment : Fragment() {
companion object {
fun newInstance() = GesturesFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentTabGesturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabGesturesBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.showMultipleEventsToggle.setCheckedImmediatelyNoEvent(Preferences.showNextEvent)
setupListener()
binding.scrollView.viewTreeObserver?.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.showNextEvent.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showMultipleEventsLabel.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
}
viewModel.calendarAppName.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.calendarAppLabel.text = when {
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)
}
}
}
}
}
viewModel.openEventDetails.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.openEventDetailsLabel.text = if (it) getString(R.string.default_event_app) else getString(R.string.default_calendar_app)
}
}
viewModel.clockAppName.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.clockAppLabel.text = when {
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)
}
}
}
}
}
viewModel.weatherAppName.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.weatherAppLabel.text = when {
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 -> getString(R.string.default_weather_app)
}
}
}
}
private fun setupListener() {
binding.actionShowMultipleEvents.setOnClickListener {
binding.showMultipleEventsToggle.isChecked = !binding.showMultipleEventsToggle.isChecked
}
binding.showMultipleEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showNextEvent = isChecked
}
binding.actionOpenEventDetails.setOnClickListener {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_event_app_title)).setSelectedValue(Preferences.openEventDetails)
.addItem(getString(R.string.default_event_app), true)
.addItem(getString(R.string.default_calendar_app), false)
.addOnSelectItemListener { value ->
Preferences.openEventDetails = value
}
.show()
}
binding.actionCalendarApp.setOnClickListener {
startActivityForResult(Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.calendarAppPackage)
}, RequestCode.CALENDAR_APP_REQUEST_CODE.code)
}
binding.actionClockApp.setOnClickListener {
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.clockAppPackage)
},
RequestCode.CLOCK_APP_REQUEST_CODE.code
)
}
binding.actionWeatherApp.setOnClickListener {
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.weatherAppPackage)
},
RequestCode.WEATHER_APP_REQUEST_CODE.code
)
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null && data.hasExtra(Constants.RESULT_APP_NAME) && data.hasExtra(Constants.RESULT_APP_PACKAGE)) {
when (requestCode) {
RequestCode.CALENDAR_APP_REQUEST_CODE.code -> {
Preferences.bulk {
calendarAppName = data.getStringExtra(Constants.RESULT_APP_NAME) ?: IntentHelper.DEFAULT_OPTION
calendarAppPackage = data.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: IntentHelper.DEFAULT_OPTION
}
}
RequestCode.EVENT_APP_REQUEST_CODE.code -> {
Preferences.bulk {
eventAppName = data.getStringExtra(Constants.RESULT_APP_NAME) ?: IntentHelper.DEFAULT_OPTION
eventAppPackage = data.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: IntentHelper.DEFAULT_OPTION
}
}
RequestCode.WEATHER_APP_REQUEST_CODE.code -> {
Preferences.bulk {
weatherAppName = data.getStringExtra(Constants.RESULT_APP_NAME) ?: IntentHelper.DEFAULT_OPTION
weatherAppPackage = data.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: IntentHelper.DEFAULT_OPTION
}
}
RequestCode.CLOCK_APP_REQUEST_CODE.code -> {
Preferences.bulk {
clockAppName = data.getStringExtra(Constants.RESULT_APP_NAME) ?: IntentHelper.DEFAULT_OPTION
clockAppPackage = data.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: IntentHelper.DEFAULT_OPTION
}
}
}
MainWidget.updateWidget(requireContext())
}
super.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -0,0 +1,513 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.animation.LayoutAnimationController
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.material.card.MaterialCardView
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.CustomNotesDialog
import com.tommasoberlose.anotherwidget.components.GlanceSettingsDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentTabGlanceBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver.Companion.FITNESS_OPTIONS
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.idik.lib.slimadapter.SlimAdapter
class GlanceTabFragment : Fragment() {
companion object {
fun newInstance() = GlanceTabFragment()
}
private var dialog: GlanceSettingsDialog? = null
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MainViewModel
private val list: ArrayList<Constants.GlanceProviderId> by lazy {
GlanceProviderHelper.getGlanceProviders(requireContext())
}
private lateinit var binding: FragmentTabGlanceBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabGlanceBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// List
binding.providersList.hasFixedSize()
binding.providersList.isNestedScrollingEnabled = false
val mLayoutManager = LinearLayoutManager(context)
binding.providersList.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<GlanceProvider>(R.layout.glance_provider_item) { item, injector ->
val provider = Constants.GlanceProviderId.from(item.id)!!
injector
.text(R.id.title, item.title)
.with<ImageView>(R.id.icon) {
it.setImageDrawable(ContextCompat.getDrawable(requireContext(), item.icon))
}
.clicked(R.id.item) {
if (provider == Constants.GlanceProviderId.CUSTOM_INFO) {
CustomNotesDialog(requireContext()){
adapter.notifyItemRangeChanged(0, adapter.data.size)
}.show()
} else {
dialog = GlanceSettingsDialog(requireActivity(), provider) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
dialog?.setOnDismissListener {
dialog = null
}
dialog?.show()
}
}
var isVisible = false
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
when {
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(
R.id.label,
if (Preferences.showMusic) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
isVisible = Preferences.showMusic
}
Preferences.showMusic -> {
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
else -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
injector.text(
R.id.label,
if (Preferences.showNextAlarm && !AlarmHelper.isAlarmProbablyWrong(
requireContext()
)
) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(
R.id.error_icon,
if (Preferences.showNextAlarm && AlarmHelper.isAlarmProbablyWrong(
requireContext()
)
) View.VISIBLE else View.GONE
)
injector.visibility(
R.id.info_icon,
if (!(Preferences.showNextAlarm && AlarmHelper.isAlarmProbablyWrong(
requireContext()
))
) View.VISIBLE else View.GONE
)
isVisible = (Preferences.showNextAlarm && !AlarmHelper.isAlarmProbablyWrong(
requireContext()
))
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
injector.text(
R.id.label,
if (Preferences.showBatteryCharging) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showBatteryCharging
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
when {
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(
R.id.label,
if (Preferences.showNotifications) getString(
R.string.settings_visible
) else getString(R.string.settings_not_visible)
)
isVisible = Preferences.showNotifications
}
Preferences.showNotifications -> {
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
else -> {
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
}
}
}
Constants.GlanceProviderId.GREETINGS -> {
injector.text(
R.id.label,
if (Preferences.showGreetings) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showGreetings
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
injector.text(
R.id.label,
if (Preferences.customNotes != "") getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.customNotes != ""
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(
context
)
if (GoogleSignIn.hasPermissions(
account,
FITNESS_OPTIONS
) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION
))
) {
injector.text(
R.id.label,
if (Preferences.showDailySteps) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showDailySteps
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.text(R.id.label, getString(R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = false
}
}
Constants.GlanceProviderId.EVENTS -> {
isVisible =
Preferences.showEventsAsGlanceProvider
val hasError = !Preferences.showEvents || !requireContext().checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
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)
injector.alpha(R.id.label, if (isVisible) 1f else .25f)
injector.alpha(R.id.icon, if (isVisible) 1f else .25f)
}
.attachTo(binding.providersList)
val mIth = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
0
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,
): Boolean {
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
// move item in `fromPos` to `toPos` in adapter.
adapter.notifyItemMoved(fromPos, toPos)
return true
}
override fun onMoved(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
fromPos: Int,
target: RecyclerView.ViewHolder,
toPos: Int,
x: Int,
y: Int
) {
with(list[toPos]) {
list[toPos] = list[fromPos]
list[fromPos] = this
}
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
GlanceProviderHelper.saveGlanceProviderOrder(
list
)
adapter.updateData(list.mapNotNull {
GlanceProviderHelper.getGlanceProviderById(
requireContext(),
it
)
})
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean,
) {
val view = viewHolder.itemView as MaterialCardView
if (isCurrentlyActive) {
ViewCompat.setElevation(view, 8f.convertDpToPixel(requireContext()))
view.setCardBackgroundColor(
ContextCompat.getColor(
requireContext(),
R.color.cardBorder
)
)
} else {
ViewCompat.setElevation(view, 0f)
view.setCardBackgroundColor(
ContextCompat.getColor(
requireContext(),
R.color.colorPrimary
)
)
}
val topEdge =
if ((view.top == 0 && dY < 0) || ((view.top + view.height >= recyclerView.height - 32f.convertDpToPixel(
requireContext()
)) && dY > 0)
) 0f else dY
super.onChildDraw(
c,
recyclerView,
viewHolder,
dX,
topEdge,
actionState,
isCurrentlyActive
)
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int,
) {
// remove from adapter
}
})
mIth.attachToRecyclerView(binding.providersList)
setupListener()
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
lifecycleScope.launch(Dispatchers.IO) {
delay(500)
val l = list.mapNotNull { GlanceProviderHelper.getGlanceProviderById(
requireContext(),
it
) }
withContext(Dispatchers.Main) {
binding.loader.animate().scaleX(0f).scaleY(0f).alpha(0f).start()
adapter.updateData(l)
val controller =
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down)
binding.providersList.layoutAnimation = controller
adapter.notifyDataSetChanged()
binding.providersList.scheduleLayoutAnimation()
}
}
}
private fun setupListener() {
}
private val nextAlarmChangeBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
}
override fun onStart() {
super.onStart()
requireActivity().registerReceiver(
nextAlarmChangeBroadcastReceiver,
IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
)
if (dialog != null) {
dialog?.show()
}
}
override fun onStop() {
requireActivity().unregisterReceiver(nextAlarmChangeBroadcastReceiver)
super.onStop()
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
when (requestCode) {
1 -> {
if (resultCode == Activity.RESULT_OK) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
} else {
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}
}
2 -> {
try {
val account: GoogleSignInAccount? = GoogleSignIn.getSignedInAccountFromIntent(
data
).getResult(ApiException::class.java)
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
GoogleSignIn.requestPermissions(
requireActivity(),
1,
account,
FITNESS_OPTIONS
)
} else {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
} catch (e: ApiException) {
e.printStackTrace()
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}
}
}
}
override fun onResume() {
super.onResume()
adapter.notifyItemRangeChanged(0, adapter.data?.size ?: 0)
if (dialog != null) {
dialog?.show()
}
}
}

View File

@ -0,0 +1,268 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
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.databinding.FragmentTabLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LayoutFragment : Fragment() {
companion object {
fun newInstance() = LayoutFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
private lateinit var binding: FragmentTabLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabLayoutBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.isDarkModeEnabled = requireActivity().isDarkTheme()
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.showDividersToggle.setCheckedImmediatelyNoEvent(Preferences.showDividers)
setupListener()
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
binding.scrollView.viewTreeObserver?.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.secondRowTopMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.secondRowTopMarginLabel.text = when (it) {
Constants.SecondRowTopMargin.NONE.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.SecondRowTopMargin.SMALL.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.SecondRowTopMargin.LARGE.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
}
viewModel.widgetAlign.observe(viewLifecycleOwner) {
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)
}
}
}
viewModel.clockBottomMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.clockBottomMarginLabel.text = when (it) {
Constants.ClockBottomMargin.NONE.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.ClockBottomMargin.SMALL.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.ClockBottomMargin.LARGE.rawValue -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
}
viewModel.backgroundCardColor.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (ColorHelper.getBackgroundAlpha(requireActivity().isDarkTheme()) == 0) {
binding.backgroundColorLabel.text = getString(R.string.transparent)
} else {
binding.backgroundColorLabel.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(requireActivity().isDarkTheme()))).toUpperCase()
}
}
}
viewModel.showDividers.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showDividersLabel.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
}
}
private fun setupListener() {
binding.actionSecondRowTopMarginSize.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_secondary_row_top_margin_title)
).setSelectedValue(Preferences.secondRowTopMargin)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_none),
Constants.SecondRowTopMargin.NONE.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_small),
Constants.SecondRowTopMargin.SMALL.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_medium),
Constants.SecondRowTopMargin.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_large),
Constants.SecondRowTopMargin.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.secondRowTopMargin = value
}.show()
}
binding.actionClockBottomMarginSize.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_clock_bottom_margin_title)
).setSelectedValue(Preferences.clockBottomMargin)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_none),
Constants.ClockBottomMargin.NONE.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_small),
Constants.ClockBottomMargin.SMALL.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_medium),
Constants.ClockBottomMargin.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_large),
Constants.ClockBottomMargin.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.clockBottomMargin = value
}.show()
}
binding.actionWidgetAlign.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_widget_align_title)
).setSelectedValue(Preferences.widgetAlign)
.addItem(
getString(R.string.settings_widget_align_center_subtitle),
Constants.WidgetAlign.CENTER.rawValue
)
.addItem(
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()
}
binding.actionBackgroundColor.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_background_color_title),
getSelected = { ColorHelper.getBackgroundColorRgb(requireActivity().isDarkTheme()) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (requireActivity().isDarkTheme()) {
Preferences.backgroundCardColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.backgroundCardColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (requireActivity().isDarkTheme()) Preferences.backgroundCardAlphaDark.toIntValue() else Preferences.backgroundCardAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (requireActivity().isDarkTheme()) {
Preferences.backgroundCardAlphaDark = alpha.toHexValue()
} else {
Preferences.backgroundCardAlpha = alpha.toHexValue()
}
}
).show()
}
binding.actionShowDividers.setOnClickListener {
binding.showDividersToggle.isChecked = !binding.showDividersToggle.isChecked
}
binding.showDividersToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showDividers = isChecked
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,206 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.Manifest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.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.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class PreferencesFragment : Fragment() {
companion object {
fun newInstance() = PreferencesFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentPreferencesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentPreferencesBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
binding.showEventsSwitch.setCheckedImmediatelyNoEvent(Preferences.showEvents)
binding.showWeatherSwitch.setCheckedImmediatelyNoEvent(Preferences.showWeather)
binding.showClockSwitch.setCheckedImmediatelyNoEvent(Preferences.showClock)
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.showEvents.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showEventsSwitch.setCheckedImmediatelyNoEvent(it)
if (it) {
CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
}
}
}
viewModel.showWeather.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
}
viewModel.weatherProviderError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
}
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
}
}
private fun setupListener() {
binding.actionTypography.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_typographyTabFragment)
}
binding.actionGeneralSettings.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_generalTabFragment)
}
binding.actionShowEvents.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_calendarTabFragment)
}
binding.showEventsSwitch.setOnCheckedChangeListener { _, enabled: Boolean ->
if (enabled) {
requireCalendarPermission()
} else {
Preferences.showEvents = enabled
}
}
binding.actionShowWeather.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_weatherTabFragment)
}
binding.showWeatherSwitch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showWeather = enabled
if (enabled) {
WeatherReceiver.setUpdates(requireContext())
} else {
WeatherReceiver.removeUpdates(requireContext())
}
}
binding.actionShowClock.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_clockTabFragment)
}
binding.showClockSwitch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showClock = enabled
}
binding.actionShowGlance.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_glanceTabFragment)
}
binding.actionTabDefaultApp.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_tabSelectorFragment_to_gesturesFragment)
}
}
private fun requireCalendarPermission() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.READ_CALENDAR
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
val granted = report.areAllPermissionsGranted()
Preferences.showEvents = granted
if (granted) {
CalendarHelper.updateEventList(requireContext())
}
}
}
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 checkWeatherProviderConfig() {
binding.weatherProviderError.isVisible = Preferences.showWeather && Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.text = Preferences.weatherProviderError
binding.weatherProviderLocationError.isVisible = Preferences.showWeather && Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.text = Preferences.weatherProviderLocationError
}
override fun onResume() {
super.onResume()
binding.showEventsSwitch.setCheckedNoEvent(Preferences.showEvents && requireActivity().checkGrantedPermission(Manifest.permission.READ_CALENDAR))
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,328 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.blockingBulk
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.FragmentTabTypographyBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.activities.tabs.CustomFontActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.CustomDateActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class TypographyFragment : Fragment() {
companion object {
fun newInstance() = TypographyFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
private lateinit var binding: FragmentTabTypographyBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabTypographyBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.isDarkModeEnabled = activity?.isDarkTheme() == true
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.textMainSize.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.mainTextSizeLabel.text = String.format("%.0fsp", it)
}
}
viewModel.textSecondSize.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.secondTextSizeLabel.text = String.format("%.0fsp", it)
}
}
viewModel.textGlobalColor.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
binding.fontColorLabel.text = getString(R.string.transparent)
} else {
binding.fontColorLabel.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(requireActivity().isDarkTheme()))).toUpperCase()
}
}
}
viewModel.textSecondaryColor.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (Preferences.textSecondaryAlpha == "00") {
binding.secondaryFontColorLabel.text = getString(R.string.transparent)
} else {
binding.secondaryFontColorLabel.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(requireActivity().isDarkTheme()))).toUpperCase()
}
}
}
viewModel.textShadow.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (requireActivity().isDarkTheme()) {
binding.textShadowLabel.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
}
viewModel.textShadow.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (!requireActivity().isDarkTheme()) {
binding.textShadowLabel.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
}
viewModel.textShadowDark.observe(viewLifecycleOwner) {
maintainScrollPosition {
if (requireActivity().isDarkTheme()) {
binding.textShadowLabel.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
}
viewModel.font.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.customFontLabel.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
}
}
viewModel.dateFormat.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.dateFormatLabel.text = DateHelper.getDateText(requireContext(), Calendar.getInstance())
}
}
}
private fun setupListener() {
binding.actionMainTextSize.setOnClickListener {
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 {
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 {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
getSelected = { ColorHelper.getFontColorRgb(requireActivity().isDarkTheme()) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (requireActivity().isDarkTheme()) {
Preferences.textGlobalColorDark = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textGlobalColor = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (requireActivity().isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue() else Preferences.textGlobalAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (requireActivity().isDarkTheme()) {
Preferences.textGlobalAlphaDark = alpha.toHexValue()
} else {
Preferences.textGlobalAlpha = alpha.toHexValue()
}
},
).show()
}
binding.actionSecondaryFontColor.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_secondary_font_color_title),
getSelected = { ColorHelper.getSecondaryFontColorRgb(requireActivity().isDarkTheme()) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (requireActivity().isDarkTheme()) {
Preferences.textSecondaryColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textSecondaryColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (requireActivity().isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue() else Preferences.textSecondaryAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (requireActivity().isDarkTheme()) {
Preferences.textSecondaryAlphaDark = alpha.toHexValue()
} else {
Preferences.textSecondaryAlpha = alpha.toHexValue()
}
},
).show()
}
binding.actionTextShadow.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.title_text_shadow)).setSelectedValue(if (requireActivity().isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow)
(2 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getTextShadowString(it)), it)
}
dialog.addOnSelectItemListener { value ->
if (requireActivity().isDarkTheme()) {
Preferences.textShadowDark = value
} else {
Preferences.textShadow = value
}
}.show()
}
binding.actionCustomFont.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_custom_font_title)).setSelectedValue(
Preferences.customFont)
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), 0), 0)
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Constants.CUSTOM_FONT_GOOGLE_SANS), Constants.CUSTOM_FONT_GOOGLE_SANS)
}
if (Preferences.customFontFile != "") {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont), Constants.CUSTOM_FONT_DOWNLOADED)
}
dialog.addItem(getString(R.string.action_custom_font_to_search), Constants.CUSTOM_FONT_DOWNLOAD_NEW)
dialog.addOnSelectItemListener { value ->
if (value == Constants.CUSTOM_FONT_DOWNLOAD_NEW) {
startActivityForResult(
Intent(requireContext(), CustomFontActivity::class.java),
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code
)
} else if (value != Constants.CUSTOM_FONT_DOWNLOADED) {
Preferences.bulk {
customFont = value
customFontFile = ""
customFontName = ""
customFontVariant = ""
}
}
}.show()
}
binding.actionDateFormat.setOnClickListener {
val now = Calendar.getInstance()
val dialog = BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_date_format_title)).setSelectedValue(Preferences.dateFormat)
dialog.addItem(DateHelper.getDefaultDateText(requireContext(), now), "")
if (Preferences.dateFormat != "") {
dialog.addItem(DateHelper.getDateText(requireContext(), now), Preferences.dateFormat)
}
dialog.addItem(getString(R.string.custom_date_format), "-")
dialog.addOnSelectItemListener { value ->
when (value) {
"-" -> {
startActivity(Intent(requireContext(), CustomDateActivity::class.java))
}
"" -> {
Preferences.blockingBulk {
isDateCapitalize = false
isDateUppercase = false
}
Preferences.dateFormat = value
}
else -> {
Preferences.dateFormat = value
}
}
}.show()
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,252 @@
package com.tommasoberlose.anotherwidget.ui.fragments.tabs
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.bulk
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.IconPackSelector
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentTabWeatherBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.tabs.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.CustomLocationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.WeatherProviderActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.collapse
import com.tommasoberlose.anotherwidget.utils.expand
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class WeatherFragment : Fragment() {
companion object {
fun newInstance() = WeatherFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentTabWeatherBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
binding = FragmentTabWeatherBinding.inflate(inflater)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
binding.scrollView.viewTreeObserver.addOnScrollChangedListener {
viewModel.fragmentScrollY.value = binding.scrollView.scrollY
}
}
private fun subscribeUi(
viewModel: MainViewModel
) {
binding.isWeatherVisible = Preferences.showWeather
viewModel.weatherProvider.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherProvider.text = WeatherHelper.getProviderName(requireContext(), Constants.WeatherProvider.fromInt(it)!!)
checkWeatherProviderConfig()
}
}
viewModel.weatherProviderError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
}
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
}
viewModel.customLocationAdd.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelCustomLocation.text =
if (it == "") getString(R.string.custom_location_gps) else it
}
checkLocationPermission()
}
viewModel.weatherTempUnit.observe(viewLifecycleOwner) {
maintainScrollPosition {
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)) {
binding.locationPermissionAlert.isVisible = false
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
binding.locationPermissionAlert.isVisible = true
binding.locationPermissionAlert.setOnClickListener {
requirePermission()
}
} else {
binding.locationPermissionAlert.isVisible = false
}
}
private fun checkWeatherProviderConfig() {
binding.weatherProviderError.isVisible = Preferences.showWeather && Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.text = Preferences.weatherProviderError
binding.weatherProviderLocationError.isVisible = Preferences.showWeather && Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.text = Preferences.weatherProviderLocationError
}
private fun setupListener() {
binding.actionWeatherProvider.setOnClickListener {
startActivityForResult(
Intent(requireContext(), WeatherProviderActivity::class.java),
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code
)
}
binding.actionCustomLocation.setOnClickListener {
startActivityForResult(
Intent(requireContext(), CustomLocationActivity::class.java),
Constants.RESULT_CODE_CUSTOM_LOCATION
)
}
binding.actionChangeUnit.setOnClickListener {
BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_unit_title)).setSelectedValue(Preferences.weatherTempUnit)
.addItem(getString(R.string.fahrenheit), "F")
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
Preferences.weatherTempUnit = value
}.show()
}
binding.actionWeatherRefreshPeriod.setOnClickListener {
val dialog =
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_weather_refresh_period_title)).setSelectedValue(Preferences.weatherRefreshPeriod)
(5 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getRefreshPeriodString(it)), it)
}
dialog
.addOnSelectItemListener { value ->
Preferences.weatherRefreshPeriod = value
}.show()
}
binding.actionWeatherIconPack.setOnClickListener {
IconPackSelector(requireContext(), header = getString(R.string.settings_weather_icon_pack_title)).show()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
WeatherReceiver.setUpdates(requireContext())
checkLocationPermission()
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
checkLocationPermission()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
checkLocationPermission()
}
}
}
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 maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
binding.scrollView.isScrollable = true
}
}
}

View File

@ -1,7 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class IntegrationsViewModel(application: Application) : AndroidViewModel(application) {
}

View File

@ -1,91 +1,177 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.core.os.ConfigurationCompat
import androidx.lifecycle.* import androidx.lifecycle.*
import com.chibatching.kotpref.livedata.asLiveData import com.chibatching.kotpref.livedata.asLiveData
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColor
import com.tommasoberlose.anotherwidget.utils.isMetric
class MainViewModel : ViewModel() { class MainViewModel(context: Application) : AndroidViewModel(context) {
// General Settings // General Settings
val textGlobalColor = Preferences.asLiveData(Preferences::textGlobalColor) val textGlobalColor = MediatorLiveData<Boolean>().apply {
val textGlobalAlpha = Preferences.asLiveData(Preferences::textGlobalAlpha) addSource(Preferences.asLiveData(Preferences::textGlobalColor)) { value = true }
val textSecondaryColor = Preferences.asLiveData(Preferences::textSecondaryColor) addSource(Preferences.asLiveData(Preferences::textGlobalAlpha)) { value = true }
val textSecondaryAlpha = Preferences.asLiveData(Preferences::textSecondaryAlpha) addSource(Preferences.asLiveData(Preferences::textGlobalColorDark)) { value = true }
val backgroundCardColor = Preferences.asLiveData(Preferences::backgroundCardColor) addSource(Preferences.asLiveData(Preferences::textGlobalAlphaDark)) { value = true }
val backgroundCardAlpha = Preferences.asLiveData(Preferences::backgroundCardAlpha) }
val textGlobalColorDark = Preferences.asLiveData(Preferences::textGlobalColorDark) val textSecondaryColor = MediatorLiveData<Boolean>().apply {
val textGlobalAlphaDark = Preferences.asLiveData(Preferences::textGlobalAlphaDark) addSource(Preferences.asLiveData(Preferences::textSecondaryColor)) { value = true }
val textSecondaryColorDark = Preferences.asLiveData(Preferences::textSecondaryColorDark) addSource(Preferences.asLiveData(Preferences::textSecondaryAlpha)) { value = true }
val textSecondaryAlphaDark = Preferences.asLiveData(Preferences::textSecondaryAlphaDark) addSource(Preferences.asLiveData(Preferences::textSecondaryColorDark)) { value = true }
val backgroundCardColorDark = Preferences.asLiveData(Preferences::backgroundCardColorDark) addSource(Preferences.asLiveData(Preferences::textSecondaryAlphaDark)) { value = true }
val backgroundCardAlphaDark = Preferences.asLiveData(Preferences::backgroundCardAlphaDark) }
val backgroundCardColor = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::backgroundCardColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardAlphaDark)) { value = true }
}
val textMainSize = Preferences.asLiveData(Preferences::textMainSize) val textMainSize = Preferences.asLiveData(Preferences::textMainSize)
val textSecondSize = Preferences.asLiveData(Preferences::textSecondSize) val textSecondSize = Preferences.asLiveData(Preferences::textSecondSize)
val textShadow = Preferences.asLiveData(Preferences::textShadow) val textShadow = Preferences.asLiveData(Preferences::textShadow)
val textShadowDark = Preferences.asLiveData(Preferences::textShadowDark) val textShadowDark = Preferences.asLiveData(Preferences::textShadowDark)
val customFont = Preferences.asLiveData(Preferences::customFont) val font = MediatorLiveData<Boolean>().apply {
val customFontFile = Preferences.asLiveData(Preferences::customFontFile) addSource(Preferences.asLiveData(Preferences::customFont)) { value = true }
val customFontName = Preferences.asLiveData(Preferences::customFontName) addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true }
val customFontVariant = Preferences.asLiveData(Preferences::customFontVariant) addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true }
}
val secondRowInformation = Preferences.asLiveData(Preferences::secondRowInformation) val secondRowInformation = Preferences.asLiveData(Preferences::secondRowInformation)
val showDividers = Preferences.asLiveData(Preferences::showDividers) val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin) val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
val widgetAlign = Preferences.asLiveData(Preferences::widgetAlign)
// Calendar Settings // Calendar Settings
val showEvents = Preferences.asLiveData(Preferences::showEvents) val showEvents = Preferences.asLiveData(Preferences::showEvents)
val calendarAllDay = Preferences.asLiveData(Preferences::calendarAllDay) val calendarAllDay = Preferences.asLiveData(Preferences::calendarAllDay)
val showUntil = Preferences.asLiveData(Preferences::showUntil) val showUntil = Preferences.asLiveData(Preferences::showUntil)
val showDiffTime = Preferences.asLiveData(Preferences::showDiffTime) val showDiffTime = Preferences.asLiveData(Preferences::showDiffTime)
val showDeclinedEvents = Preferences.asLiveData(Preferences::showDeclinedEvents)
val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent) val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent)
val showNextEventOnMultipleLines = Preferences.asLiveData(Preferences::showNextEventOnMultipleLines)
val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails) val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails)
val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName) val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName)
val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency) val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency)
val showOnlyBusyEvents = Preferences.asLiveData(Preferences::showOnlyBusyEvents) val dateFormat = Preferences.asLiveData(Preferences::dateFormat)
// Clock Settings // Clock Settings
val showClock = Preferences.asLiveData(Preferences::showClock) val showClock = Preferences.asLiveData(Preferences::showClock)
val clockTextSize = Preferences.asLiveData(Preferences::clockTextSize) val clockTextSize = Preferences.asLiveData(Preferences::clockTextSize)
val clockTextColor = Preferences.asLiveData(Preferences::clockTextColor) val altTimezoneLabel = Preferences.asLiveData(Preferences::altTimezoneLabel)
val clockTextAlpha = Preferences.asLiveData(Preferences::clockTextAlpha) val clockTextColor = MediatorLiveData<Boolean>().apply {
val clockTextColorDark = Preferences.asLiveData(Preferences::clockTextColorDark) addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
val clockTextAlphaDark = Preferences.asLiveData(Preferences::clockTextAlphaDark) addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
}
val clockTextColorDark = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::clockTextColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlphaDark)) { value = true }
}
val showAMPMIndicator = Preferences.asLiveData(Preferences::showAMPMIndicator) val showAMPMIndicator = Preferences.asLiveData(Preferences::showAMPMIndicator)
val clockAppName = Preferences.asLiveData(Preferences::clockAppName) val clockAppName = Preferences.asLiveData(Preferences::clockAppName)
val dateFormat = Preferences.asLiveData(Preferences::dateFormat)
val clockBottomMargin = Preferences.asLiveData(Preferences::clockBottomMargin) val clockBottomMargin = Preferences.asLiveData(Preferences::clockBottomMargin)
val showBigClockWarning = Preferences.asLiveData(Preferences::showBigClockWarning)
// Weather Settings // Weather Settings
val showWeather = Preferences.asLiveData(Preferences::showWeather) val showWeather = Preferences.asLiveData(Preferences::showWeather)
val weatherTempUnit = Preferences.asLiveData(Preferences::weatherTempUnit) val weatherTempUnit = Preferences.asLiveData(Preferences::weatherTempUnit)
val weatherRefreshPeriod = Preferences.asLiveData(Preferences::weatherRefreshPeriod) val weatherRefreshPeriod = Preferences.asLiveData(Preferences::weatherRefreshPeriod)
val weatherAppName = Preferences.asLiveData(Preferences::weatherAppName) val weatherAppName = Preferences.asLiveData(Preferences::weatherAppName)
val weatherProviderApi = Preferences.asLiveData(Preferences::weatherProviderApiOpen)
val customLocationAdd = Preferences.asLiveData(Preferences::customLocationAdd) val customLocationAdd = Preferences.asLiveData(Preferences::customLocationAdd)
val showWeatherWarning = Preferences.asLiveData(Preferences::showWeatherWarning)
val weatherIconPack = Preferences.asLiveData(Preferences::weatherIconPack) val weatherIconPack = Preferences.asLiveData(Preferences::weatherIconPack)
val weatherProvider = Preferences.asLiveData(Preferences::weatherProvider) val weatherProvider = Preferences.asLiveData(Preferences::weatherProvider)
val weatherProviderError = Preferences.asLiveData(Preferences::weatherProviderError) val weatherProviderError = Preferences.asLiveData(Preferences::weatherProviderError)
val weatherProviderLocationError = Preferences.asLiveData(Preferences::weatherProviderLocationError) val weatherProviderLocationError = Preferences.asLiveData(Preferences::weatherProviderLocationError)
// Glance
val showGlance = Preferences.asLiveData(Preferences::showGlance)
val showMusic = Preferences.asLiveData(Preferences::showMusic)
val showNextAlarm = Preferences.asLiveData(Preferences::showNextAlarm)
val showBatteryCharging = Preferences.asLiveData(Preferences::showBatteryCharging)
val showDailySteps = Preferences.asLiveData(Preferences::showDailySteps)
val customInfo = Preferences.asLiveData(Preferences::customNotes)
val musicPlayersFilter = Preferences.asLiveData(Preferences::musicPlayersFilter)
// Advanced Settings // Advanced Settings
val darkThemePreference = Preferences.asLiveData(Preferences::darkThemePreference) val darkThemePreference = Preferences.asLiveData(Preferences::darkThemePreference)
val showWallpaper = Preferences.asLiveData(Preferences::showWallpaper) val showWallpaper = Preferences.asLiveData(Preferences::showWallpaper)
val showPreview = Preferences.asLiveData(Preferences::showPreview) val showPreview = Preferences.asLiveData(Preferences::showPreview)
val installedIntegrations = Preferences.asLiveData(Preferences::installedIntegrations) val installedIntegrations = Preferences.asLiveData(Preferences::installedIntegrations)
// UI
val fragmentScrollY = MutableLiveData<Int>()
val clockPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::clockTextSize)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlphaDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::showAMPMIndicator)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockBottomMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::altTimezoneLabel)) { value = true }
}
val widgetPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::textGlobalColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalAlphaDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::dateFormat)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryAlphaDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::backgroundCardAlphaDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::textMainSize)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondSize)) { value = true }
addSource(Preferences.asLiveData(Preferences::textShadow)) { value = true }
addSource(Preferences.asLiveData(Preferences::textShadowDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFont)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true }
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::showDividers)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowTopMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::isDateCapitalize)) { value = true }
addSource(Preferences.asLiveData(Preferences::isDateUppercase)) { value = true }
addSource(Preferences.asLiveData(Preferences::showEvents)) { value = true }
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 }
addSource(Preferences.asLiveData(Preferences::showOnlyBusyEvents)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherTempUnit)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLon)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationAdd)) { value = true }
addSource(Preferences.asLiveData(Preferences::enabledGlanceProviderOrder)) { value = true }
addSource(Preferences.asLiveData(Preferences::customNotes)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNextAlarm)) { value = true }
addSource(Preferences.asLiveData(Preferences::showBatteryCharging)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDailySteps)) { value = true }
addSource(Preferences.asLiveData(Preferences::showGreetings)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNotifications)) { value = true }
addSource(Preferences.asLiveData(Preferences::showMusic)) { value = true }
addSource(Preferences.asLiveData(Preferences::mediaInfoFormat)) { value = true }
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::installedIntegrations)) { value = true }
}
} }

View File

@ -0,0 +1,7 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels.settings
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class IntegrationsViewModel(application: Application) : AndroidViewModel(application) {
}

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.settings
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent

View File

@ -1,11 +1,9 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.util.Log
import androidx.lifecycle.* import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo

View File

@ -0,0 +1,11 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
class MediaInfoFormatViewModel(application: Application) : AndroidViewModel(application) {
val mediaInfoFormatInput: MutableLiveData<String> = MutableLiveData(if (Preferences.mediaInfoFormat == "") MediaPlayerHelper.DEFAULT_MEDIA_INFO_FORMAT else Preferences.mediaInfoFormat)
}

View File

@ -1,11 +1,9 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.util.Log
import androidx.lifecycle.* import androidx.lifecycle.*
import com.chibatching.kotpref.livedata.asLiveData import com.chibatching.kotpref.livedata.asLiveData
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences

View File

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

View File

@ -1,4 +1,4 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels package com.tommasoberlose.anotherwidget.ui.viewmodels.tabs
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel

View File

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

View File

@ -0,0 +1,109 @@
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
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
class ClockWidget(val context: Context) {
fun updateClockView(views: RemoteViews, widgetID: Int): RemoteViews {
try {
if (!Preferences.showClock) {
views.setViewVisibility(R.id.time, View.GONE)
views.setViewVisibility(R.id.time_am_pm, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_none, View.GONE)
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()))
views.setTextViewTextSize(
R.id.time,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context)
)
views.setTextViewTextSize(
R.id.time_am_pm,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 5 * 2
)
val clockPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
0
)
views.setOnClickPendingIntent(R.id.time, clockPIntent)
views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent)
views.setViewVisibility(R.id.time, View.VISIBLE)
views.setViewVisibility(R.id.time_am_pm, if (Preferences.showAMPMIndicator) View.VISIBLE else View.GONE)
views.setViewVisibility(
R.id.clock_bottom_margin_none,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_small,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_medium,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_large,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
// Timezones
if (Preferences.altTimezoneId != "" && Preferences.altTimezoneLabel != "") {
views.setString(R.id.alt_timezone_time, "setTimeZone", Preferences.altTimezoneId)
views.setString(R.id.alt_timezone_time_am_pm, "setTimeZone", Preferences.altTimezoneId)
views.setTextViewText(R.id.alt_timezone_label, Preferences.altTimezoneLabel)
views.setTextColor(R.id.alt_timezone_time, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.alt_timezone_time_am_pm, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.alt_timezone_label, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextViewTextSize(
R.id.alt_timezone_time,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 3
)
views.setTextViewTextSize(
R.id.alt_timezone_time_am_pm,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
views.setTextViewTextSize(
R.id.alt_timezone_label,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
views.setOnClickPendingIntent(R.id.timezones_container, clockPIntent)
views.setViewVisibility(R.id.timezones_container, View.VISIBLE)
} else {
views.setViewVisibility(R.id.timezones_container, View.GONE)
}
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
}

View File

@ -0,0 +1,975 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
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.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
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.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 com.tommasoberlose.anotherwidget.utils.toPixel
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class StandardWidget(val context: Context) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews? {
var views = RemoteViews(context.packageName, R.layout.the_widget_sans)
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
views = ClockWidget(context).updateClockView(views, appWidgetId)
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface) ?: return null
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateGridView(bindingView: TheWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false, 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, 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 = 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)
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false, width = bindingView.subLine.width, height = bindingView.subLine.height)
)
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventsCount > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false, 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
)
)
// Action previous event
views.setImageViewBitmap(
R.id.action_previous_rect,
BitmapHelper.getBitmapFromView(bindingView.actionPrevious, draw = false, width = bindingView.actionPrevious.width, height = bindingView.actionPrevious.height)
)
views.setViewVisibility(R.id.action_previous_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_previous_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setViewVisibility(R.id.action_previous_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
views.setViewVisibility(R.id.action_previous_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,
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 = 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, width = bindingView.nextEvent.width, height = bindingView.nextEvent.height)
)
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)
} 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.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false, width = bindingView.subLine.width, height = bindingView.subLine.height)
)
views.setViewVisibility(R.id.first_line_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)
}
} 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)
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding? {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
val bindingView = TheWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
// 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.actionPrevious.isVisible = false
bindingView.date.text = DateHelper.getDateText(context, now)
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventsCount > 1
bindingView.actionPrevious.isVisible =
Preferences.showNextEvent && eventsCount > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showNextEventOnMultipleLines) {
bindingView.nextEvent.apply {
isSingleLine = false
maxLines = 3
gravity = Gravity.CENTER
}
}
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 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 =
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 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.weatherDateLineDivider,
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, bindingView.actionPrevious)
} else {
listOf<ImageView>(
bindingView.actionNext,
bindingView.actionPrevious,
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.weatherDateLineDivider to (Preferences.textMainSize - 2),
bindingView.weatherDateLineTemperature to Preferences.textMainSize,
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 / 18f
bindingView.weatherDateLineWeatherIcon.scaleY = Preferences.textMainSize / 18f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f
bindingView.actionPrevious.scaleX = Preferences.textMainSize / 28f
bindingView.actionPrevious.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.weatherDateLineDivider,
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),
Pair(bindingView.actionPrevious, bindingView.actionPreviousShadow),
).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)
}
}
bindingView.actionPrevious.scaleX = bindingView.actionPrevious.scaleX * -1
bindingView.actionPreviousShadow.scaleX = bindingView.actionPreviousShadow.scaleX * -1
// 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.weatherDateLineDivider,
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.weatherDateLineDivider,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = typeface
}
}
// Dividers
arrayOf(bindingView.weatherDateLineDivider, 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
}
}
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null
}
}
}

View File

@ -2,7 +2,6 @@ package com.tommasoberlose.anotherwidget.utils
import android.animation.* import android.animation.*
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
import android.widget.Toast import android.widget.Toast
@ -22,20 +21,14 @@ import android.content.res.Resources
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.TypedValue import android.util.TypedValue
import android.view.ViewPropertyAnimator import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.Transformation
import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.UiThread
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.core.animation.addListener import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.components.OnSingleClickListener
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import kotlinx.android.synthetic.main.fragment_app_main.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import java.util.* import java.util.*
@ -148,7 +141,7 @@ fun View.collapse(duration: Long = 500L) {
fun Context.openURI(url: String) { fun Context.openURI(url: String) {
try { try {
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
builder.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary)) builder.setDefaultColorSchemeParams(CustomTabColorSchemeParams.Builder().setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary)).build())
val customTabsIntent: CustomTabsIntent = builder.build() val customTabsIntent: CustomTabsIntent = builder.build()
customTabsIntent.launchUrl(this, Uri.parse(url)) customTabsIntent.launchUrl(this, Uri.parse(url))
} catch (e: Exception) { } catch (e: Exception) {
@ -256,4 +249,27 @@ fun Intent.isDefaultSet(context: Context): Boolean {
} catch (ex: java.lang.Exception) { } catch (ex: java.lang.Exception) {
false false
} }
} }
fun Locale.isMetric(): Boolean {
return when (country.toUpperCase(this)) {
"US", "LR", "MM", "GB" -> false
else -> true
}
}
fun View.setOnSingleClickListener(l: View.OnClickListener) {
setOnClickListener(OnSingleClickListener(l))
}
fun View.setOnSingleClickListener(l: (View) -> Unit) {
setOnClickListener(OnSingleClickListener(l))
}
fun ignoreExceptions(function: () -> Unit) = run {
try {
function.invoke()
} catch (ex: Exception) {
ex.printStackTrace()
}
}

View File

@ -0,0 +1,26 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="400">
<translate
android:fromYDelta="-20%"
android:toYDelta="0"
android:interpolator="@android:anim/decelerate_interpolator"
/>
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:interpolator="@android:anim/decelerate_interpolator"
/>
<scale
android:fromXScale="105%"
android:fromYScale="105%"
android:toXScale="100%"
android:toYScale="100%"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/decelerate_interpolator"
/>
</set>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/item_animation_fall_down"
android:delay="15%"
android:animationOrder="normal"
/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

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