Compare commits

...

137 Commits

Author SHA1 Message Date
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
49ca17803e Update translations 2020-10-27 18:21:55 +01:00
97ab72081d Update the UI 2020-10-27 18:13:27 +01:00
e6fee0dfe1 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-27 18:13:15 +01:00
e0c4f24c43 Merge pull request #239 from Vgbhieel/master
Added Portuguese translation (values-pt)
2020-10-27 18:13:02 +01:00
2a7d0f171b Create strings,xml for portuguese values
translated all strings to portuguese
2020-10-23 00:55:39 -03:00
00dcfc3149 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-20 10:39:19 +02:00
eb11b603aa Merge pull request #235 from Moutony/patch-24
Update French 2
2020-10-20 10:39:01 +02:00
4d2f624448 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-20 10:38:47 +02:00
7581af4dd2 Merge branch 'english-strings' 2020-10-20 10:38:19 +02:00
0325af6582 Fix uppercase labels 2020-10-20 10:35:54 +02:00
4de0413a35 Fix multiple bugs 2020-10-20 10:27:05 +02:00
c54a24c889 Update strings.xml 2020-10-19 21:54:51 +02:00
98eccd2833 Update strings.xml 2020-10-19 21:54:17 +02:00
0119a20765 Update strings.xml 2020-10-19 21:51:12 +02:00
a1e54892fd Update strings.xml 2020-10-19 16:28:38 +02:00
17801fd164 Update strings.xml 2020-10-19 16:26:57 +02:00
e6087b2969 Merge pull request #234 from Moutony/patch-23
Update French
2020-10-19 15:35:06 +02:00
59b8ba26a2 Merge pull request #232 from d-l-n/master
Added and translated new strings
2020-10-19 15:35:01 +02:00
f190ee5d15 Merge pull request #231 from Drumber/translation
Update German translation
2020-10-19 15:34:48 +02:00
2f266ffcf8 Update strings.xml 2020-10-19 11:07:05 +02:00
0e376aba80 Update strings.xml 2020-10-19 11:06:29 +02:00
313e4fa92d Update French 2020-10-19 11:01:21 +02:00
a8ec754bc4 Added and translated new strings 2020-10-19 01:04:33 -03:00
f8d1188634 Update strings.xml
Added greetings and notification timeout strings
2020-10-18 18:47:52 +02:00
8216fc96b9 Update English strings.xml
Removed OpenWeatherMap former instructions.
Capitalized all action buttons.
name="action_save" translatable="false">OK
2020-10-18 16:15:22 +02:00
56d95c5559 Update the bundle version 2020-10-18 12:58:13 +02:00
c0e747a714 Add greetings as glance provider 2020-10-18 12:51:02 +02:00
16076dc145 Fix #225 2020-10-18 11:21:52 +02:00
c95f9fb943 Fix #222 2020-10-18 11:20:40 +02:00
331d5772af Added the shadow to the icons 2020-10-16 18:04:30 +02:00
e2719b6445 Fix #181 2020-10-16 12:31:32 +02:00
bd35022f7d Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-16 11:58:38 +02:00
6d9cb750a7 Merge pull request #220 from Moutony/patch-20
Update strings.xml (English) for syntax
2020-10-16 11:58:27 +02:00
f85b4c9a6a Merge pull request #223 from Drumber/translation
Update German translation
2020-10-16 11:58:22 +02:00
df54a4a79e Merge pull request #221 from Moutony/patch-21
Update French
2020-10-16 11:58:10 +02:00
6ea97e7724 Fix the events order 2020-10-16 11:58:02 +02:00
d27071739d Update strings.xml 2020-10-15 22:59:12 +02:00
738781225f Update strings.xml
Added missing strings and updated some translations
2020-10-15 20:03:53 +02:00
12d32f852b Update French
Shortened some sentences
2020-10-15 19:10:17 +02:00
f0baa60363 Update strings.xml 2020-10-15 18:43:35 +02:00
785eb39334 Update strings.xml (English) for syntax
<string name="weather_warning">
2020-10-15 18:15:48 +02:00
d289699d08 Merge pull request #213 from Moutony/patch-17
Update strings.xml (English) for UI optimization
2020-10-15 10:12:58 +02:00
c68d0a8327 Merge pull request #215 from Moutony/patch-19
Update strings.xml (French)
2020-10-15 10:12:49 +02:00
de2e223713 Merge pull request #214 from chreddy/patch-3
Updating Danish translation
2020-10-15 10:12:15 +02:00
6150dd7e22 Fix #217 2020-10-15 10:10:38 +02:00
d9ecebe770 Update strings.xml 2020-10-14 22:30:44 +02:00
52327715b1 Update strings.xml
Change the data provider priority by sorting the list below with the drag-and-drop icons.
2020-10-14 22:23:28 +02:00
e301e6e6ec Update strings.xml
Grammar
2020-10-14 22:11:56 +02:00
c884fae362 Update strings.xml
Added (EN) to Legal & Privacy
2020-10-14 20:42:29 +02:00
eb78257e52 Update strings.xml
Fixed some old things too.
Update 2 of today.
2020-10-14 20:37:30 +02:00
109aa24af1 Update strings.xml (English) for UI optimization
I added periods for some sentences (error notice, full sentence with subject-verb-object)
I shortened some sentences that where on multiple rows with a single word in the last row.
Line 131: I changed grammar to be more correct.
Project header: I added Open-source to header, in order to shorten the Feedback subtitle.
2020-10-14 19:57:27 +02:00
2403f066a3 Updating Danish translation 2020-10-14 19:50:11 +02:00
9bc7d7b62e Update strings.xml (English) for UI optimization
I added periods for some sentences (error notice, full sentence with subject-verb-object)
I shortened some sentences that where on multiple rows with a single word in the last row.
Project header: I added Open-source to header, in order to shorten the Feedback subtitle.
2020-10-14 19:31:15 +02:00
a1a6f9f607 Fix the privacy policy error 2020-10-14 14:56:37 +02:00
2907 changed files with 13476 additions and 8318 deletions

5
.gitignore vendored
View File

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

View File

@ -123,6 +123,29 @@
</PersistentState>
</value>
</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>
</option>
</component>

Binary file not shown.

View File

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

140
.idea/navEditor.xml generated
View File

@ -46,6 +46,146 @@
</LayoutPositions>
</value>
</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>
</option>
</component>

View File

@ -6,7 +6,6 @@ apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android'
@ -16,18 +15,20 @@ apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android {
compileSdkVersion 29
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23
targetSdkVersion 29
versionCode 107
versionName "2.0.15"
targetSdkVersion 30
versionCode 130
versionName "2.3.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
renderscriptSupportModeEnabled true
}
buildTypes {
@ -53,68 +54,69 @@ android {
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
}
dataBinding {
enabled = true
}
viewBinding.enabled = true
buildFeatures {
dataBinding = true
viewBinding = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
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.espresso:espresso-core:3.3.0'
// UI
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.3.0'
implementation 'net.idik:slimadapter:2.1.2'
implementation 'com.google.android:flexbox:2.0.1'
implementation 'com.kyleduo.switchbutton:library:2.0.3'
// Lifecycle
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.work:work-runtime-ktx:2.4.0"
implementation "androidx.work:work-runtime-ktx:2.5.0"
// EventBus
implementation 'org.greenrobot:eventbus:3.2.0'
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// Other
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 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
//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'
// Fitness
implementation 'com.google.android.gms:play-services-fitness:18.0.0'
implementation 'com.google.android.gms:play-services-auth:18.1.0'
implementation 'com.google.android.gms:play-services-fitness:20.0.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
//Weather
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
implementation 'com.android.billingclient:billing:3.0.1'
implementation 'com.android.billingclient:billing-ktx:3.0.1'
implementation 'com.android.billingclient:billing:3.0.3'
implementation 'com.android.billingclient:billing-ktx:3.0.3'
// KTX
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.core:core-ktx:1.3.2'
@ -126,14 +128,14 @@ dependencies {
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1"
//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.
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
// Preferences
implementation 'com.chibatching.kotpref:kotpref:2.11.0'
implementation 'com.chibatching.kotpref:livedata-support:2.10.0'
implementation 'com.chibatching.kotpref:kotpref:2.13.1'
implementation 'com.chibatching.kotpref:livedata-support:2.13.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
// 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.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.RECEIVE_BOOT_COMPLETED" />
<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.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
@ -32,16 +31,17 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.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.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">
<intent-filter>
@ -151,9 +151,21 @@
</receiver>
<service
android:name=".services.UpdateCalendarJob"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
android:name=".services.UpdateCalendarService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application>
<queries>
<package android:name="com.google.android.apps.fitness"/>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
</manifest>

View File

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

View File

@ -6,6 +6,7 @@ import android.graphics.Color
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.GridLayout
import android.widget.ImageView
@ -21,18 +22,17 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuHorBinding
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuListBinding
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.reveal
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
import kotlinx.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 net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
@ -52,23 +52,22 @@ class BottomSheetColorPicker(
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() {
val view = View.inflate(context, R.layout.bottom_sheet_menu_hor, null)
window?.setDimAmount(0f)
// Header
view.header.isVisible = header != null
view.header_text.text = header ?: ""
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
// Alpha
view.alpha_selector_container.isVisible = showAlphaSelector
view.alpha_selector.setProgress(alpha.toFloat())
view.text_alpha.text = "%s: %s%%".format(context.getString(R.string.alpha), alpha)
view.alpha_selector.onSeekChangeListener = object : OnSeekChangeListener {
binding.alphaSelectorContainer.isVisible = showAlphaSelector
binding.alphaSelector.setProgress(alpha.toFloat())
binding.textAlpha.text = "%s: %s%%".format(context.getString(R.string.alpha), alpha)
binding.alphaSelector.onSeekChangeListener = object : OnSeekChangeListener {
override fun onSeeking(seekParams: SeekParams?) {
seekParams?.let {
view.text_alpha.text = "%s: %s%%".format(context.getString(R.string.alpha), it.progress)
binding.textAlpha.text = "%s: %s%%".format(context.getString(R.string.alpha), it.progress)
onAlphaChangeListener?.invoke(it.progress)
}
}
@ -83,16 +82,16 @@ class BottomSheetColorPicker(
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
val listView = View.inflate(context, R.layout.bottom_sheet_menu_list, null) as RecyclerView
listView.setHasFixedSize(true)
listBinding.root.setHasFixedSize(true)
val mLayoutManager = GridLayoutManager(context, 6)
listView.layoutManager = mLayoutManager
listBinding.root.layoutManager = mLayoutManager
adapter
.register<Int>(R.layout.color_picker_menu_item) { item, injector ->
injector
.with<MaterialCardView>(R.id.color) {
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) {
if (getSelected?.invoke() == item) {
@ -113,22 +112,22 @@ class BottomSheetColorPicker(
onColorSelected?.invoke(item)
val position = adapter.data.indexOf(item)
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())
withContext(Dispatchers.Main) {
view.color_loader.isVisible = false
view.list_container.addView(listView)
binding.colorLoader.isVisible = false
binding.listContainer.addView(listBinding.root)
this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
view.list_container.isVisible = true
binding.listContainer.isVisible = true
}
})
setContentView(view)
setContentView(binding.root)
super.show()
}

View File

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

View File

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

View File

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

View File

@ -7,9 +7,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.View
import androidx.core.app.NotificationManagerCompat
import android.view.LayoutInflater
import androidx.core.view.isVisible
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
@ -21,25 +19,30 @@ 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.GlanceProviderSettingsLayoutBinding
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.GreetingsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.ui.activities.AppNotificationsFilterActivity
import com.tommasoberlose.anotherwidget.ui.activities.MusicPlayersFilterActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.AppNotificationsFilterActivity
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.utils.checkGrantedPermission
import kotlinx.android.synthetic.main.glance_provider_settings_layout.view.*
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
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() {
val view = View.inflate(context, R.layout.glance_provider_settings_layout, null)
/* 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.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)
@ -47,10 +50,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> context.getString(R.string.settings_daily_steps_title)
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_title)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_title)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_title)
}
/* 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.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)
@ -58,58 +62,86 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> context.getString(R.string.settings_daily_steps_subtitle)
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_subtitle)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_subtitle)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_subtitle)
}
/* 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) {
view.action_filter_music_players.setOnClickListener {
context.startActivity(Intent(context, MusicPlayersFilterActivity::class.java))
binding.actionFilterMusicPlayers.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, MusicPlayersFilterActivity::class.java), 0)
}
checkNotificationPermission(view)
binding.actionChangeMediaInfoFormat.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, MediaInfoFormatActivity::class.java), 0)
}
checkNotificationPermission()
}
/* 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) {
view.header.text = context.getString(R.string.information_header)
view.warning_container.isVisible = false
checkNextAlarm(view)
binding.header.text = context.getString(R.string.information_header)
binding.warningContainer.isVisible = false
checkNextAlarm()
}
/* 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) {
view.warning_container.isVisible = false
checkFitnessPermission(view)
checkGoogleFitConnection(view)
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
/* BATTERY INFO */
if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) {
view.warning_container.isVisible = false
view.header.isVisible = false
view.divider.isVisible = false
binding.warningContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
}
/* NOTIFICATIONS */
view.action_filter_notifications_app.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
binding.actionFilterNotificationsApp.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
binding.actionChangeNotificationTimer.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
if (provider == Constants.GlanceProviderId.NOTIFICATIONS) {
checkLastNotificationsPermission(view)
view.action_filter_notifications_app.setOnClickListener {
context.startActivity(Intent(context, AppNotificationsFilterActivity::class.java))
checkLastNotificationsPermission()
val stringArray = context.resources.getStringArray(R.array.glance_notifications_timeout)
binding.actionFilterNotificationsApp.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, AppNotificationsFilterActivity::class.java), 0)
}
binding.notificationTimerLabel.text = stringArray[Preferences.hideNotificationAfter]
binding.actionChangeNotificationTimer.setOnClickListener {
val dialog = BottomSheetMenu<Int>(context, header = context.getString(R.string.glance_notification_hide_timeout_title)).setSelectedValue(Preferences.hideNotificationAfter)
Constants.GlanceNotificationTimer.values().forEachIndexed { index, timeout ->
dialog.addItem(stringArray[index], timeout.rawValue)
}
dialog.addOnSelectItemListener { value ->
Preferences.hideNotificationAfter = value
this.show()
}.show()
}
}
/* GREETINGS */
if (provider == Constants.GlanceProviderId.GREETINGS) {
view.warning_container.isVisible = false
view.header.isVisible = false
view.divider.isVisible = false
binding.warningContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
}
/* EVENTS */
if (provider == Constants.GlanceProviderId.EVENTS) {
binding.header.isVisible = false
binding.divider.isVisible = false
checkCalendarConfig()
}
/* TOGGLE */
view.provider_switch.isChecked = when (provider) {
binding.providerSwitch.setCheckedImmediatelyNoEvent(when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> Preferences.showNextAlarm
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> Preferences.showBatteryCharging
@ -117,11 +149,12 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> Preferences.showDailySteps
Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications
Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings
}
Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider
})
var job: Job? = null
view.provider_switch.setOnCheckedChangeListener { _, isChecked ->
binding.providerSwitch.setOnCheckedChangeListener { _, isChecked ->
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
delay(300)
@ -129,21 +162,22 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
Preferences.showMusic = isChecked
checkNotificationPermission(view)
checkNotificationPermission()
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked
checkNextAlarm(view)
checkNextAlarm()
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked
checkLastNotificationsPermission(view)
checkLastNotificationsPermission()
}
Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked
GreetingsHelper.toggleGreetings(context)
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (isChecked) {
@ -167,9 +201,12 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Preferences.showDailySteps = false
}
view.warning_container.isVisible = false
checkFitnessPermission(view)
checkGoogleFitConnection(view)
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked
}
else -> {
}
@ -179,11 +216,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
}
setContentView(view)
setContentView(binding.root)
super.show()
}
private fun checkNextAlarm(view: View) {
private fun checkNextAlarm() {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) {
@ -193,56 +230,71 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} catch (e: Exception) {
alarm.showIntent?.creatorPackage ?: ""
}
view.alarm_set_by_title.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)
view.alarm_set_by_title.isVisible = true
binding.alarmSetByTitle.text = context.getString(R.string.settings_show_next_alarm_app_title).format(appNameOrPackage)
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)
binding.alarmSetByContainer.isVisible = true
} else {
view.alarm_set_by_title.isVisible = false
binding.alarmSetByContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
}
}
statusCallback?.invoke()
}
private fun checkNotificationPermission(view: View) {
private fun checkCalendarConfig() {
if (!Preferences.showEvents || !context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_show_events_as_glance_provider_error)
binding.warningContainer.setOnClickListener {
dismiss()
EventBus.getDefault().post(MainFragment.ChangeTabEvent(1))
}
} else {
binding.warningContainer.isVisible = false
}
}
private fun checkNotificationPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
view.warning_container.isVisible = false
binding.warningContainer.isVisible = false
MediaPlayerHelper.updatePlayingMediaInfo(context)
}
Preferences.showMusic -> {
view.warning_container.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_notification_access)
view.warning_container.setOnClickListener {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_notification_access)
binding.warningContainer.setOnClickListener {
context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
}
else -> {
view.warning_container.isVisible = false
binding.warningContainer.isVisible = false
}
}
statusCallback?.invoke()
}
private fun checkLastNotificationsPermission(view: View) {
private fun checkLastNotificationsPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
view.warning_container.isVisible = false
binding.warningContainer.isVisible = false
}
Preferences.showNotifications -> {
view.warning_container.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_last_notification_access)
view.warning_container.setOnClickListener {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_last_notification_access)
binding.warningContainer.setOnClickListener {
context.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
}
else -> {
view.warning_container.isVisible = false
binding.warningContainer.isVisible = false
}
}
statusCallback?.invoke()
}
private fun checkFitnessPermission(view: View) {
private fun checkFitnessPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION)
) {
@ -253,10 +305,10 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(context)
view.warning_container.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_fitness_access)
view.warning_container.setOnClickListener {
requireFitnessPermission(view)
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
requireFitnessPermission()
}
} else {
ActivityDetectionReceiver.unregisterFence(context)
@ -264,36 +316,36 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
statusCallback?.invoke()
}
private fun checkGoogleFitConnection(view: View) {
private fun checkGoogleFitConnection() {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS
)) {
view.warning_container.isVisible = true
view.warning_title.text = context.getString(R.string.settings_request_fitness_access)
view.warning_container.setOnClickListener {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
view.action_connect_to_google_fit.isVisible = true
view.action_disconnect_to_google_fit.isVisible = false
view.action_connect_to_google_fit.setOnClickListener {
binding.actionConnectToGoogleFit.isVisible = true
binding.actionDisconnectToGoogleFit.isVisible = false
binding.actionConnectToGoogleFit.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
view.action_disconnect_to_google_fit.setOnClickListener(null)
view.google_fit_status_label.text = context.getString(R.string.google_fit_account_not_connected)
binding.actionDisconnectToGoogleFit.setOnClickListener(null)
binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_not_connected)
} else {
view.action_connect_to_google_fit.isVisible = false
view.action_disconnect_to_google_fit.isVisible = true
view.action_connect_to_google_fit.setOnClickListener(null)
view.action_disconnect_to_google_fit.setOnClickListener {
binding.actionConnectToGoogleFit.isVisible = false
binding.actionDisconnectToGoogleFit.isVisible = true
binding.actionConnectToGoogleFit.setOnClickListener(null)
binding.actionDisconnectToGoogleFit.setOnClickListener {
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS
@ -301,11 +353,11 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
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)
.withPermissions(
"com.google.android.gms.permission.ACTIVITY_RECOGNITION",
@ -313,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"
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
checkFitnessPermission(view)
checkFitnessPermission()
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,

View File

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

View File

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

@ -18,7 +18,7 @@ import kotlin.collections.ArrayList
class EventRepository(val context: Context) {
private val realm by lazy { Realm.getDefaultInstance() }
fun saveEvents(eventList: ArrayList<Event>) {
fun saveEvents(eventList: List<Event>) {
realm.executeTransaction { realm ->
realm.where(Event::class.java).findAll().deleteAllFromRealm()
realm.copyToRealm(eventList)

View File

@ -2,7 +2,6 @@ package com.tommasoberlose.anotherwidget.global
object Actions {
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_ALARM_UPDATE = "com.tommasoberlose.anotherwidget.action.ALARM_UPDATE"
@ -13,4 +12,7 @@ object Actions {
const val ACTION_GO_TO_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT"
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_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
import java.text.SimpleDateFormat
object Constants {
const val RESULT_CODE_CUSTOM_LOCATION = 45
const val RESULT_APP_NAME = "RESULT_APP_NAME"
@ -11,14 +9,14 @@ object Constants {
const val CUSTOM_FONT_DOWNLOADED = 2
const val CUSTOM_FONT_DOWNLOAD_NEW = 3
enum class ClockBottomMargin(val value: Int) {
enum class ClockBottomMargin(val rawValue: Int) {
NONE(0),
SMALL(1),
MEDIUM(2),
LARGE(3)
}
enum class SecondRowTopMargin(val value: Int) {
enum class SecondRowTopMargin(val rawValue: Int) {
NONE(0),
SMALL(1),
MEDIUM(2),
@ -32,7 +30,8 @@ object Constants {
CUSTOM_INFO("CUSTOM_INFO"),
GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS"),
NOTIFICATIONS("NOTIFICATIONS"),
GREETINGS("GREETINGS");
GREETINGS("GREETINGS"),
EVENTS("EVENTS");
companion object {
private val map = GlanceProviderId.values().associateBy(GlanceProviderId::id)
@ -40,13 +39,13 @@ object Constants {
}
}
enum class WidgetUpdateFrequency(val value: Int) {
enum class WidgetUpdateFrequency(val rawValue: Int) {
LOW(0),
DEFAULT(1),
HIGH(2)
}
enum class WeatherProvider(val value: Int) {
enum class WeatherProvider(val rawValue: Int) {
OPEN_WEATHER(0),
WEATHER_BIT(1),
WEATHER_API(2),
@ -56,15 +55,35 @@ object Constants {
YR(6);
companion object {
private val map = WeatherProvider.values().associateBy(WeatherProvider::value)
private val map = WeatherProvider.values().associateBy(WeatherProvider::rawValue)
fun fromInt(type: Int) = map[type]
}
}
enum class WeatherIconPack(val value: Int) {
enum class GlanceNotificationTimer(val rawValue: Int) {
HALF_MINUTE(0),
ONE_MINUTE(1),
FIVE_MINUTES(2),
TEN_MINUTES(3),
FIFTEEN_MINUTES(4),
WHEN_DISMISSED(5);
companion object {
private val map = values().associateBy(GlanceNotificationTimer::rawValue)
fun fromInt(type: Int) = map[type]
}
}
enum class WeatherIconPack(val rawValue: Int) {
DEFAULT(0),
MINIMAL(1),
COOL(2),
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
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate.*
import androidx.core.os.ConfigurationCompat
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() {
override val commitAllPropertiesByDefault: Boolean = true
@ -14,8 +17,8 @@ object Preferences : KotprefModel() {
var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false)
var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "")
var weatherTemp by floatPref(key = "PREF_WEATHER_TEMP", default = 0f)
var weatherTempUnit by stringPref(key = "PREF_WEATHER_TEMP_UNIT", default = "F")
var weatherRealTempUnit by stringPref(key = "PREF_WEATHER_REAL_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 = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) "C" else "F")
var calendarAllDay by booleanPref(key = "PREF_CALENDAR_ALL_DAY", default = true)
var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "")
@ -43,14 +46,14 @@ object Preferences : KotprefModel() {
var weatherProviderApiWeatherApi by stringPref(default = "")
var weatherProviderApiWeatherBit 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 weatherProviderLocationError by stringPref(default = "")
var eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "")
var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "")
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 textGlobalAlpha by stringPref(default = "FF")
@ -79,14 +82,18 @@ object Preferences : KotprefModel() {
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
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 90f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.value)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.value)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")
@ -106,18 +113,17 @@ object Preferences : KotprefModel() {
var showDividers by booleanPref(default = true)
var widgetAlign by intPref(default = Constants.WidgetAlign.CENTER.rawValue)
// Settings
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 showXiaomiWarning by booleanPref(default = true)
// Glance
var showGlance by booleanPref(default = true)
var enabledGlanceProviderOrder 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 isBatteryLevelLow by booleanPref(default = false)
var isCharging by booleanPref(default = false)
@ -125,6 +131,7 @@ object Preferences : KotprefModel() {
var showDailySteps by booleanPref(default = false)
var showGreetings by booleanPref(default = false)
var showNotifications by booleanPref(default = false)
var hideNotificationAfter by intPref(default = Constants.GlanceNotificationTimer.ONE_MINUTE.rawValue)
var lastNotificationId by intPref(default = -1)
var lastNotificationTitle by stringPref(default = "")
@ -132,14 +139,16 @@ object Preferences : KotprefModel() {
var lastNotificationPackage by stringPref(default = "")
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 mediaPlayerAlbum 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 appNotificationsFilter by stringPref(default = "")
var showEventsAsGlanceProvider by booleanPref(default = false)
// Integrations
var installedIntegrations by intPref(default = 0)
}

View File

@ -9,6 +9,7 @@ import com.chibatching.kotpref.Kotpref
import com.chibatching.kotpref.blockingBulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.NotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
object ActiveNotificationsHelper {
fun showLastNotification(): Boolean {
@ -23,6 +24,7 @@ object ActiveNotificationsHelper {
remove(Preferences::lastNotificationPackage)
remove(Preferences::lastNotificationIcon)
}
MainWidget.updateWidget(context)
}
fun checkNotificationAccess(context: Context): Boolean {

View File

@ -1,29 +1,16 @@
package com.tommasoberlose.anotherwidget.helpers
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.Intent
import android.provider.CalendarContract
import android.util.Log
import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.services.UpdateCalendarJob
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.services.UpdateCalendarService
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
/**
@ -32,7 +19,7 @@ import kotlin.collections.ArrayList
object CalendarHelper {
fun updateEventList(context: Context) {
UpdateCalendarJob.enqueueWork(context, Intent())
UpdateCalendarService.enqueueWork(context)
}
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -80,4 +67,27 @@ object CalendarHelper {
.filter { (!Preferences.showOnlyBusyEvents || it.availability != CalendarContract.EventsEntity.AVAILABILITY_FREE) }
.toList()
}
fun List<Event>.sortEvents(): List<Event> {
return sortedWith { event: Event, event1: Event ->
val date = Calendar.getInstance().apply { timeInMillis = event.startDate }
val date1 = Calendar.getInstance().apply { timeInMillis = event1.startDate }
if (date.get(Calendar.DAY_OF_YEAR) == date1.get(Calendar.DAY_OF_YEAR) && date.get(
Calendar.YEAR) == date1.get(Calendar.YEAR)
) {
if (event.allDay && event1.allDay) {
event.startDate.compareTo(event1.startDate)
} else if (event.allDay) {
1
} else if (event1.allDay) {
-1
} else {
event.startDate.compareTo(event1.startDate)
}
} else {
event.startDate.compareTo(event1.startDate)
}
}
}
}

View File

@ -2,14 +2,16 @@ package com.tommasoberlose.anotherwidget.helpers
import android.annotation.SuppressLint
import android.graphics.Color
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Preferences
import kotlin.math.roundToInt
object ColorHelper {
fun getFontColor(isDark: Boolean): Int {
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) {
Color.parseColor("#FFFFFFFF")
}
@ -33,7 +35,10 @@ object ColorHelper {
fun getSecondaryFontColor(isDark: Boolean): Int {
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) {
Color.parseColor("#FFFFFFFF")
}
@ -57,7 +62,10 @@ object ColorHelper {
fun getClockFontColor(isDark: Boolean): Int {
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) {
Color.parseColor("#FFFFFFFF")
}
@ -81,7 +89,10 @@ object ColorHelper {
fun getBackgroundColor(isDark: Boolean): Int {
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) {
Color.parseColor("#00000000")
}
@ -123,4 +134,14 @@ object ColorHelper {
val hexValue = this.toInt(16).toDouble()
return (hexValue * 100 / 255).roundToInt()
}
fun String.isColor(): Boolean {
return try {
Color.parseColor(this)
true
} catch (iae: IllegalArgumentException) {
iae.printStackTrace()
false
}
}
}

View File

@ -1,11 +1,14 @@
package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.content.Context
import android.util.Log
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.checkIfFitInstalled
import java.util.ArrayList
@ -16,9 +19,10 @@ object GlanceProviderHelper {
val providers = Constants.GlanceProviderId.values()
.filter {
context.checkIfFitInstalled() || it != Constants.GlanceProviderId.GOOGLE_FIT_STEPS
}.toTypedArray()
}
.toTypedArray()
providers.sortWith(Comparator { p1, p2 ->
return ArrayList(providers.filter { enabledProviders.contains(it.id) }.sortedWith(Comparator { p1, p2 ->
when {
enabledProviders.contains(p1.id) && enabledProviders.contains(p2.id) -> {
enabledProviders.indexOf(p1.id).compareTo(enabledProviders.indexOf(p2.id))
@ -33,9 +37,7 @@ object GlanceProviderHelper {
p1.id.compareTo(p2.id)
}
}
})
return ArrayList(providers.toList())
}) + providers.filter { !enabledProviders.contains(it.id) })
}
fun getGlanceProviderById(context: Context, providerId: Constants.GlanceProviderId): GlanceProvider? {
@ -43,49 +45,55 @@ object GlanceProviderHelper {
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_next_alarm_title),
R.drawable.round_access_alarm
R.drawable.round_access_alarm_24
)
}
Constants.GlanceProviderId.PLAYING_SONG -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_music_title),
R.drawable.round_radio
R.drawable.round_music_note_24
)
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
GlanceProvider(providerId.id,
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 -> {
GlanceProvider(providerId.id,
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 -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_daily_steps_title),
R.drawable.round_run_circle
R.drawable.round_favorite_border_24
)
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_notifications_title),
R.drawable.round_notifications
R.drawable.round_notifications_24
)
}
Constants.GlanceProviderId.GREETINGS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_greetings_title),
R.drawable.round_history_edu
R.drawable.round_history_edu_24
)
}
Constants.GlanceProviderId.EVENTS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_events_as_glance_provider_title),
R.drawable.round_event_note_24
)
}
}
}
fun saveGlanceProviderOrder(list: ArrayList<Constants.GlanceProviderId>) {
fun saveGlanceProviderOrder(list: List<Constants.GlanceProviderId>) {
Preferences.enabledGlanceProviderOrder = list.joinToString(separator = ",")
}
@ -93,14 +101,17 @@ object GlanceProviderHelper {
val eventRepository = EventRepository(context)
BatteryHelper.updateBatteryInfo(context)
val showGlance = Preferences.showGlance && (eventRepository.getEventsCount() == 0 || !Preferences.showEvents)
val showGlance = (eventRepository.getEventsCount() == 0 || !Preferences.showEvents || Preferences.showEventsAsGlanceProvider)
&& (
(Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) ||
(Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") ||
(MediaPlayerHelper.isSomeonePlaying(context)) ||
(Preferences.showBatteryCharging && Preferences.isCharging || Preferences.isBatteryLevelLow) ||
(Preferences.customNotes.isNotEmpty()) ||
(Preferences.showDailySteps && Preferences.googleFitSteps > 0)
(Preferences.showDailySteps && Preferences.googleFitSteps > 0) ||
(Preferences.showGreetings && GreetingsHelper.showGreetings()) ||
(Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && eventRepository.getNextEvent() != null)
)
eventRepository.close()
return showGlance

View File

@ -0,0 +1,110 @@
package com.tommasoberlose.anotherwidget.helpers
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import java.util.*
object GreetingsHelper {
private const val MORNING_TIME = 36
private const val MORNING_TIME_END = 37
private const val EVENING_TIME = 38
private const val NIGHT_TIME = 39
fun toggleGreetings(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
}
if (Preferences.showGreetings) {
setRepeating(
AlarmManager.RTC,
now.apply {
set(Calendar.HOUR_OF_DAY, 5)
}.timeInMillis,
1000 * 60 * 60 * 24,
PendingIntent.getBroadcast(context,
MORNING_TIME,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
)
setRepeating(
AlarmManager.RTC,
now.apply {
set(Calendar.HOUR_OF_DAY, 9)
}.timeInMillis,
1000 * 60 * 60 * 24,
PendingIntent.getBroadcast(context,
MORNING_TIME_END,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
)
setRepeating(
AlarmManager.RTC,
now.apply {
set(Calendar.HOUR_OF_DAY, 19)
}.timeInMillis,
1000 * 60 * 60 * 24,
PendingIntent.getBroadcast(context,
EVENING_TIME,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
)
setRepeating(
AlarmManager.RTC,
now.apply {
set(Calendar.HOUR_OF_DAY, 22)
}.timeInMillis,
1000 * 60 * 60 * 24,
PendingIntent.getBroadcast(context,
NIGHT_TIME,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
)
} else {
listOf(MORNING_TIME, MORNING_TIME_END, EVENING_TIME, NIGHT_TIME).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context,
UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
}, 0))
}
}
}
}
fun showGreetings(): Boolean {
val hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
return hour < 9 || hour >= 19
}
fun getRandomString(context: Context): String {
val hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
val array = when {
hour in 5..8 -> context.resources.getStringArray(R.array.morning_greetings)
hour in 19..21 -> context.resources.getStringArray(R.array.evening_greetings)
hour >= 22 && hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
else -> emptyArray()
}
return if (array.isNotEmpty()) array[Random().nextInt(array.size)] else ""
}
}

View File

@ -0,0 +1,84 @@
package com.tommasoberlose.anotherwidget.helpers
import android.content.Context
import android.graphics.*
import android.renderscript.*
import android.util.TypedValue
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import java.util.prefs.Preferences
import kotlin.math.min
object ImageHelper {
fun ImageView.applyShadow(originalView: ImageView, factor: Float = 1f) {
clearColorFilter()
val cElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, when (if (context.isDarkTheme()) com.tommasoberlose.anotherwidget.global.Preferences.textShadowDark else com.tommasoberlose.anotherwidget.global.Preferences.textShadow) {
0 -> 0f * factor
1 -> 8f * factor
2 -> 16f * factor
else -> 0f * factor
}, resources.displayMetrics)
if (originalView.drawable != null && originalView.drawable.intrinsicWidth > 0 && originalView.drawable.intrinsicHeight > 0) {
val btm = originalView.drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true)
val comb = Bitmap.createBitmap(btm)
val shadowBitmap = generateShadowBitmap(context, cElevation, btm, factor)
shadowBitmap?.let {
val canvas = Canvas(comb)
canvas.drawColor(Color.TRANSPARENT)
canvas.save()
val rect = Rect()
// val bounds = originalView.drawable.copyBounds()
canvas.getClipBounds(rect)
rect.inset(-2 * getBlurRadius(context, cElevation).toInt(), -2 * getBlurRadius(context, cElevation).toInt())
canvas.save()
canvas.clipRect(rect)
canvas.drawBitmap(shadowBitmap, 0f, 2f, null)
canvas.restore()
setImageBitmap(comb)
}
}
}
private fun generateShadowBitmap(context: Context, cElevation: Float, bitmap: Bitmap?, factor: Float): Bitmap? {
val rs: RenderScript = RenderScript.create(context)
val element = Element.U8_4(rs)
val blurScript: ScriptIntrinsicBlur = ScriptIntrinsicBlur.create(rs, element)
val colorMatrixScript: ScriptIntrinsicColorMatrix = ScriptIntrinsicColorMatrix.create(rs)
val allocationIn = Allocation.createFromBitmap(rs, bitmap)
val allocationOut = Allocation.createTyped(rs, allocationIn.type)
val matrix = Matrix4f(floatArrayOf(
0f, 0f, 0f, 0f,
0f, 0f, 0f, 0f,
0f, 0f, 0f, 0f,
0f, 0f, 0f, when (if (context.isDarkTheme()) com.tommasoberlose.anotherwidget.global.Preferences.textShadowDark else com.tommasoberlose.anotherwidget.global.Preferences.textShadow) {
0 -> 0f * factor
1 -> 0.8f * factor
2 -> 1f * factor
else -> 0f
}))
colorMatrixScript.setColorMatrix(matrix)
colorMatrixScript.forEach(allocationIn, allocationOut)
blurScript.setRadius(getBlurRadius(context, cElevation))
blurScript.setInput(allocationOut)
blurScript.forEach(allocationIn)
allocationIn.copyTo(bitmap)
allocationIn.destroy()
allocationOut.destroy()
return bitmap
}
private fun getBlurRadius(context: Context, customElevation: Float): Float {
val maxElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f, context.resources.displayMetrics)
return min(25f * (customElevation / maxElevation), 25f)
}
}

View File

@ -12,8 +12,10 @@ import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import android.util.Log
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast
import java.util.*
@ -21,6 +23,10 @@ import java.util.*
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 {
val widgetManager = AppWidgetManager.getInstance(context)
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 {
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=$address")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
@ -47,17 +60,21 @@ object IntentHelper {
fun getWeatherIntent(context: Context): Intent {
return when (Preferences.weatherAppPackage) {
"" -> {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
setClassName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
}
}
"_" -> {
DO_NOTHING_OPTION -> {
Intent()
}
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> {
val pm: PackageManager = context.packageManager
try {
@ -66,7 +83,6 @@ object IntentHelper {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
@ -80,14 +96,17 @@ object IntentHelper {
.appendPath(Calendar.getInstance().timeInMillis.toString())
.build()
return when (Preferences.calendarAppPackage) {
"" -> {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
data = calendarUri
}
}
"_" -> {
DO_NOTHING_OPTION -> {
Intent()
}
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> {
val pm: PackageManager = context.packageManager
try {
@ -96,7 +115,6 @@ object IntentHelper {
data = calendarUri
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
@ -166,14 +184,17 @@ object IntentHelper {
fun getClockIntent(context: Context): Intent {
return when (Preferences.clockAppPackage) {
"" -> {
DEFAULT_OPTION -> {
Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
"_" -> {
DO_NOTHING_OPTION -> {
Intent()
}
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> {
val pm: PackageManager = context.packageManager
try {
@ -181,7 +202,6 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
@ -194,7 +214,7 @@ object IntentHelper {
fun getMusicIntent(context: Context): Intent {
return when (Preferences.mediaPlayerPackage) {
"" -> {
DO_NOTHING_OPTION -> {
Intent()
}
else -> {
@ -204,7 +224,6 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
@ -218,7 +237,6 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
@ -230,7 +248,6 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}

View File

@ -15,14 +15,34 @@ import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import java.lang.Exception
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 getMediaInfo(): String {
return if (Preferences.mediaPlayerArtist == "") {
Preferences.mediaPlayerTitle
} else {
"%s, %s".format(Preferences.mediaPlayerTitle, Preferences.mediaPlayerArtist)
}
fun getMediaInfo(format: String = Preferences.mediaInfoFormat, title: String = Preferences.mediaPlayerTitle, artist: String = Preferences.mediaPlayerArtist, album: String = Preferences.mediaPlayerAlbum): String {
return when (format) {
"",
DEFAULT_MEDIA_INFO_FORMAT -> {
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) {

View File

@ -63,10 +63,20 @@ object SettingsStringHelper {
}
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("200") -> context.getString(R.string.font_200)
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("600") -> context.getString(R.string.font_600)
variant.contains("700") -> context.getString(R.string.font_700)
@ -86,13 +96,13 @@ object SettingsStringHelper {
difference <= 0 -> {
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()
}
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()
}
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)
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 -> {

View File

@ -2,22 +2,15 @@ package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.content.Context
import android.os.Build
import android.util.Log
import com.chibatching.kotpref.Kotpref
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
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.utils.checkGrantedPermission
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)
if (Preferences.customLocationAdd != "") {
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)) {
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener { task ->
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())
}
}
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
LocationService.requestNewLocation(context)
}
}
@ -139,97 +114,97 @@ object WeatherHelper {
return when (icon) {
"01d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.clear_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.clear_day_2
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
}
}
"02d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.partly_cloudy_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.partly_cloudy_2
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
}
}
"03d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.mostly_cloudy_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.mostly_cloudy_2
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
}
}
"04d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.cloudy_weather_2
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
}
}
"09d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.storm_weather_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.storm_weather_day_2
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
}
}
"10d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rainy_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rainy_day_2
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
}
}
"11d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.thunder_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.thunder_day_2
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
}
}
"13d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.snow_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.snow_day_2
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
}
}
"50d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_day_2
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
}
}
"80d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.windy_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.windy_day_2
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
}
}
"81d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_day_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rain_snow_day_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rain_snow_day_2
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
}
}
"82d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_weather_2
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
}
}
@ -238,97 +213,97 @@ object WeatherHelper {
"01n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.clear_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.clear_night_2
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
}
}
"02n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.partly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.partly_cloudy_night_2
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
}
}
"03n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.mostly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.mostly_cloudy_night_2
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
}
}
"04n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.cloudy_weather_2
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
}
}
"09n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.storm_weather_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.storm_weather_night_2
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
}
}
"10n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rainy_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rainy_night_2
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
}
}
"11n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.thunder_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.thunder_night_2
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
}
}
"13n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.snow_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.snow_night_2
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
}
}
"50n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_night_2
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
}
}
"80n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.windy_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.windy_night_2
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
}
}
"81n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_night_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.rain_snow_night_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.rain_snow_night_2
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
}
}
"82n" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
Constants.WeatherIconPack.COOL.rawValue -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.rawValue -> R.drawable.haze_weather_2
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
}
}

View File

@ -25,9 +25,8 @@ object WidgetHelper {
) {
fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
val isPortrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT
val width = getWidgetWidth(isPortrait, widgetId)
val height = getWidgetHeight(isPortrait, widgetId)
val width = getWidgetWidth(widgetId)
val height = getWidgetHeight(widgetId)
val widthInPx = context.dip(width)
val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
@ -35,9 +34,9 @@ object WidgetHelper {
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 =
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.util.Log
import com.chibatching.kotpref.Kotpref
import com.google.gson.internal.LinkedTreeMap
import com.haroldadmin.cnradapter.NetworkResponse
import com.haroldadmin.cnradapter.executeWithRetry
@ -26,6 +27,7 @@ import java.util.*
class WeatherNetworkApi(val context: Context) {
suspend fun updateWeather() {
Kotpref.init(context)
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
@ -344,7 +346,7 @@ class WeatherNetworkApi(val context: Context) {
private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") {
val repository = AccuweatherRepository()
// val repository = AccuweatherRepository()
// when (val response = repository.getWeather()) {
// is NetworkResponse.Success -> {

View File

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

View File

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

View File

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

View File

@ -8,11 +8,15 @@ import android.os.Build
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import android.widget.Toast
import com.google.gson.Gson
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import java.lang.Exception
import java.util.*
@ -31,16 +35,20 @@ class NotificationListener : NotificationListenerService() {
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName)) {
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
Preferences.lastNotificationId = sbn.id
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Preferences.lastNotificationIcon = sbn.notification.smallIcon.resId
Preferences.lastNotificationPackage = sbn.notification.smallIcon.resPackage
} else {
Preferences.lastNotificationIcon = sbn.notification.icon
Preferences.lastNotificationPackage = sbn.packageName
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Preferences.lastNotificationIcon = sbn.notification.smallIcon.resId
} else {
@Suppress("DEPRECATION")
Preferences.lastNotificationIcon = sbn.notification.icon
}
} catch (ex: Exception) {
Preferences.lastNotificationIcon = 0
}
Preferences.lastNotificationPackage = sbn.packageName
MainWidget.updateWidget(this)
setTimeout(this)
}
@ -69,16 +77,26 @@ class NotificationListener : NotificationListenerService() {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 28943, intent, 0))
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + 30 * 1000,
PendingIntent.getBroadcast(
context,
5,
intent,
0
val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter)
if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) {
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + when (timeoutPref) {
Constants.GlanceNotificationTimer.HALF_MINUTE -> 30 * 1000
Constants.GlanceNotificationTimer.ONE_MINUTE -> 60 * 1000
Constants.GlanceNotificationTimer.FIVE_MINUTES -> 5 * 60 * 1000
Constants.GlanceNotificationTimer.TEN_MINUTES -> 10 * 60 * 1000
Constants.GlanceNotificationTimer.FIFTEEN_MINUTES -> 15 * 60 * 1000
else -> 0
},
PendingIntent.getBroadcast(
context,
5,
intent,
0
)
)
)
}
}
}
}

View File

@ -5,19 +5,16 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
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.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.BatteryHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.models.Event
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 java.util.*
@ -33,9 +30,9 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_DATE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> {
CalendarHelper.updateEventList(context)
ActiveNotificationsHelper.clearLastNotification(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
CalendarHelper.updateEventList(context)
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
@ -52,6 +49,17 @@ class UpdatesReceiver : BroadcastReceiver() {
ActiveNotificationsHelper.clearLastNotification(context)
MainWidget.updateWidget(context)
}
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context)
}
Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context)
}
}
}
}
@ -99,7 +107,7 @@ class UpdatesReceiver : BroadcastReceiver() {
if (diff.hours == 0) {
var minutes = 0
when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.value -> {
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
minutes = when {
diff.minutes > 50 -> 50
diff.minutes > 30 -> 30
@ -107,7 +115,7 @@ class UpdatesReceiver : BroadcastReceiver() {
else -> 0
}
}
Constants.WidgetUpdateFrequency.HIGH.value -> {
Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
minutes = diff.minutes - (diff.minutes % 5)
}
}

View File

@ -6,6 +6,7 @@ import android.content.Intent
import android.net.Uri
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.utils.toast
@ -15,19 +16,20 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) {
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) {
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 {
context.applicationContext.startActivity(IntentHelper.getWeatherIntent(context.applicationContext))
} catch (e: Exception) {
val uri = Uri.parse("http://www.google.com/#q=weather")
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))
}
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,143 +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.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 {
timeInMillis = begin.timeInMillis
add(Calendar.DAY_OF_YEAR, 2)
}
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 filteredEventList = eventList
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventList.sortWith(Comparator { event: Event, event1: Event ->
val date = Calendar.getInstance().apply { timeInMillis = event.startDate }
val date1 = Calendar.getInstance().apply { timeInMillis = event1.startDate }
if (date.get(Calendar.DAY_OF_YEAR) == date1.get(Calendar.DAY_OF_YEAR) && date.get(Calendar.YEAR) == date1.get(Calendar.YEAR)) {
if (event.allDay && event1.allDay) {
event.startDate.compareTo(event1.startDate)
} else if (event.allDay) {
1
} else if (event1.allDay) {
-1
} else {
event.startDate.compareTo(event1.startDate)
}
} else {
event.startDate.compareTo(event1.startDate)
}
})
eventRepository.saveEvents(
eventList
)
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,147 +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
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 = SlimAdapter.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,190 +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 {
"__"
}
if (viewModel.isDateCapitalize.value == true) {
text = text.getCapWordString()
}
if (viewModel.isDateUppercase.value == true) {
text = text.toUpperCase(Locale.getDefault())
}
withContext(Dispatchers.Main) {
action_save.isVisible = text != ERROR_STRING
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_save.setOnClickListener {
Preferences.blockingBulk {
dateFormat = viewModel.dateInput.value ?: ""
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
finish()
}
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")
}
}
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
import android.Manifest
import android.animation.ValueAnimator
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
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.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.RelativeLayout
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.lifecycleScope
import androidx.navigation.NavController
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.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.ActivityMainBinding
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.BitmapHelper
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.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.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() {
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private var mAppWidgetId: Int = -1
private lateinit var viewModel: MainViewModel
private lateinit var binding: ActivityMainBinding
private val mainNavController: NavController? by lazy {
Navigation.findNavController(
this,
R.id.content_fragment
)
}
private val settingsNavController: NavController? by lazy {
Navigation.findNavController(
this,
R.id.settings_fragment
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding = ActivityMainBinding.inflate(layoutInflater)
controlExtras(intent)
if (Preferences.showWallpaper) {
requirePermission()
}
setContentView(binding.root)
}
override fun onBackPressed() {
if (mainNavController?.currentDestination?.id == R.id.appMainFragment) {
if (mAppWidgetId > 0) {
addNewWidget()
if (settingsNavController?.navigateUp() == false) {
if (mAppWidgetId > 0) {
addNewWidget()
} else {
setResult(Activity.RESULT_OK)
finish()
}
} else {
setResult(Activity.RESULT_OK)
finish()
viewModel.fragmentScrollY.value = 0
}
} else {
super.onBackPressed()
@ -110,8 +91,8 @@ class MainActivity : AppCompatActivity() {
AppWidgetManager.INVALID_APPWIDGET_ID)
if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
action_add_widget.visibility = View.VISIBLE
action_add_widget.setOnClickListener {
binding.actionAddWidget.visibility = View.VISIBLE
binding.actionAddWidget.setOnClickListener {
addNewWidget()
}
}
@ -153,4 +134,26 @@ class MainActivity : AppCompatActivity() {
})
.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,183 +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) {
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.value
updateListItem(oldValue)
updateListItem()
loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.clicked(R.id.radioButton) {
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.util.Log
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode.OK
import com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivitySupportDevBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.SupportDevViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.settings.SupportDevViewModel
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.activity_support_dev.*
import net.idik.lib.slimadapter.SlimAdapter
class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var viewModel: SupportDevViewModel
private lateinit var adapter: SlimAdapter
private lateinit var binding: ActivitySupportDevBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(SupportDevViewModel::class.java)
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)
list_view.layoutManager = mLayoutManager
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
@ -62,20 +53,22 @@ class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
viewModel.purchase(this, item)
}
}
.attachTo(list_view)
.attachTo(binding.listView)
viewModel.openConnection()
subscribeUi(viewModel)
action_back.setOnClickListener {
binding.actionBack.setOnClickListener {
onBackPressed()
}
setContentView(binding.root)
}
private fun subscribeUi(viewModel: SupportDevViewModel) {
viewModel.products.observe(this, Observer {
if (it.isNotEmpty()) {
loader.isVisible = false
binding.loader.isVisible = false
}
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.os.Bundle
@ -6,7 +6,6 @@ import android.view.View
import android.widget.ImageView
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
@ -16,8 +15,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityAppNotificationsFilterBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.AppNotificationsViewModel
import kotlinx.android.synthetic.main.activity_app_notifications_filter.*
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.AppNotificationsViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
@ -26,16 +24,17 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: AppNotificationsViewModel
private lateinit var binding: ActivityAppNotificationsFilterBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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)
list_view.layoutManager = mLayoutManager
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
@ -60,12 +59,14 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
}
.checked(R.id.checkBox, ActiveNotificationsHelper.isAppAccepted(item.activityInfo.packageName))
}
.attachTo(list_view)
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
binding.search.requestFocus()
setContentView(binding.root)
}
private var filterJob: Job? = null
@ -76,22 +77,22 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
viewModel.appList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
binding.loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
clear_search.isVisible = search.isNotBlank()
binding.clearSearch.isVisible = search.isNotBlank()
})
viewModel.appNotificationsFilter.observe(this, {
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) {
loader.visibility = View.VISIBLE
binding.loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
@ -117,22 +118,22 @@ class AppNotificationsFilterActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE
binding.loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
binding.actionBack.setOnClickListener {
onBackPressed()
}
clear_search.setOnClickListener {
binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = ""
}
clear_selection.setOnClickListener {
binding.clearSelection.setOnClickListener {
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.content.Intent
@ -6,6 +6,7 @@ import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
@ -13,28 +14,20 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
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.google.gson.Gson
import com.koolio.library.Font
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomFontBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.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 com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.CustomFontViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.diff.DefaultDiffCallback
@ -44,19 +37,17 @@ class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel
private lateinit var binding: ActivityCustomFontBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCustomFontBinding>(
this,
R.layout.activity_custom_font
)
binding = ActivityCustomFontBinding.inflate(layoutInflater)
list_view.setHasFixedSize(true)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter.enableDiff(object: DefaultDiffCallback() {
@ -86,7 +77,7 @@ class CustomFontActivity : AppCompatActivity() {
injector.clicked(R.id.text) {
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.addOnSelectItemListener { value ->
@ -136,7 +127,7 @@ class CustomFontActivity : AppCompatActivity() {
if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
} else {
item.fontVariants.filter { !it.contains("italic") }
item.fontVariants
.forEachIndexed { index, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), index)
}
@ -146,12 +137,14 @@ class CustomFontActivity : AppCompatActivity() {
}.show()
}
}
.attachTo(list_view)
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
binding.search.requestFocus()
setContentView(binding.root)
}
private var filterJob: Job? = null
@ -162,12 +155,12 @@ class CustomFontActivity : AppCompatActivity() {
viewModel.fontList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
binding.loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { 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,
search: String? = viewModel.searchInput.value
) {
loader.visibility = View.VISIBLE
binding.loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
@ -209,18 +202,18 @@ class CustomFontActivity : AppCompatActivity() {
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE
binding.loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
binding.actionBack.setOnClickListener {
onBackPressed()
}
clear_search.setOnClickListener {
binding.clearSearch.setOnClickListener {
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,151 @@
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 = item.locality
}
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.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.tommasoberlose.anotherwidget.ui.fragments.*
import com.tommasoberlose.anotherwidget.ui.fragments.tabs.*
class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
@ -12,11 +12,11 @@ class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
override fun createFragment(position: Int): Fragment {
return when (position) {
1 -> CalendarTabFragment.newInstance()
2 -> WeatherTabFragment.newInstance()
3 -> ClockTabFragment.newInstance()
1 -> CalendarFragment.newInstance()
2 -> WeatherFragment.newInstance()
3 -> ClockFragment.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,416 +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.app.NotificationManagerCompat
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
import java.util.*
class GlanceTabFragment : Fragment() {
companion object {
fun newInstance() = GlanceTabFragment()
}
private var dialog: GlanceSettingsDialog? = null
private lateinit var adapter: SlimAdapter
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<FragmentGlanceSettingsBinding>(inflater,
R.layout.fragment_glance_settings,
container,
false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
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()).show()
} else {
dialog = GlanceSettingsDialog(requireActivity(), provider) {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
dialog?.setOnDismissListener {
dialog = null
}
dialog?.show()
}
}
}
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))
}
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))
}
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))
}
}
}
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)
}
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)
}
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))
}
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))
}
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))
}
}
}
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)
}
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)
}
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)
} 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))
} 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)
}
}
}
}
.attachTo(providers_list)
val mIth = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
0
) {
val list = GlanceProviderHelper.getGlanceProviders(requireContext())
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)
Collections.swap(list, fromPos, toPos)
return true
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
GlanceProviderHelper.saveGlanceProviderOrder(list)
}
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
Log.d("ciao", "${view.top} + ${view.height} = ${view.top + view.height} to compare to ${recyclerView.height} - ${32f.convertDpToPixel(requireContext())}")
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(
GlanceProviderHelper.getGlanceProviders(requireContext())
.mapNotNull { GlanceProviderHelper.getGlanceProviderById(requireContext(), it) }
.filterNot { it.id == Constants.GlanceProviderId.GREETINGS.id }
)
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
}
}
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,61 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.animation.ValueAnimator
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.core.animation.addListener
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
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.badge.BadgeDrawable
import com.google.android.material.tabs.TabLayoutMediator
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentAppMainBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
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.widgets.MainWidget
import com.tommasoberlose.anotherwidget.ui.widgets.StandardWidget
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 org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener {
class MainFragment : Fragment() {
companion object {
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 binding: FragmentAppMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
}
override fun onCreateView(
@ -66,41 +63,7 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
return inflater.inflate(R.layout.fragment_app_main, container, false)
}
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)
binding = FragmentAppMainBinding.inflate(inflater)
// Warnings
if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) {
@ -117,6 +80,104 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
}
.show()
}
val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
navHost?.navController?.addOnDestinationChangedListener { controller, destination, _ ->
val show = destination.id != R.id.tabSelectorFragment
binding.actionBack.animate().alpha(if (show) 1f else 0f).setDuration(200).translationX((if (show) 0f else 4f).convertDpToPixel(requireContext())).start()
binding.actionBack.setOnSingleClickListener {
controller.navigateUp()
}
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)
}
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) {
viewModel.showWallpaper.observe(viewLifecycleOwner) {
if (it) {
val wallpaper = requireActivity().getCurrentWallpaper()
binding.widgetBg.setImageDrawable(if (it) wallpaper else null)
if (wallpaper != null) {
binding.widgetBg.layoutParams =
(binding.widgetBg.layoutParams as ViewGroup.MarginLayoutParams).apply {
val metrics = DisplayMetrics()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val display = requireActivity().display
display?.getRealMetrics(metrics)
} else {
@Suppress("DEPRECATION")
val display = requireActivity().windowManager.defaultDisplay
@Suppress("DEPRECATION")
display.getMetrics(metrics)
}
val dimensions: Pair<Int, Int> =
if (wallpaper.intrinsicWidth >= wallpaper.intrinsicHeight) {
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
height = dimensions.second
}
}
} else {
binding.widgetBg.setImageDrawable(null)
}
}
viewModel.fragmentScrollY.observe(viewLifecycleOwner) {
binding.toolbar.cardElevation = if (it > 0) 24f else 0f
}
viewModel.widgetAlign.observe(viewLifecycleOwner) {
updatePreviewVisibility()
lifecycleScope.launch {
delay(350)
updateClock()
}
}
viewModel.showPreview.observe(viewLifecycleOwner) {
updatePreviewVisibility()
}
viewModel.clockPreferencesUpdate.observe(viewLifecycleOwner) {
updateClock()
}
viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) {
onUpdateUiEvent(null)
}
viewModel.showClock.observe(viewLifecycleOwner) {
updateClockVisibility(it)
}
}
private var uiJob: Job? = null
@ -124,286 +185,196 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
private fun updateUI() {
uiJob?.cancel()
preview?.clearAnimation()
time_container?.clearAnimation()
if (Preferences.showPreview) {
preview?.setCardBackgroundColor(
ContextCompat.getColor(
lifecycleScope.launch(Dispatchers.IO) {
val bgColor: Int = ContextCompat.getColor(
requireContext(),
if (ColorHelper.getFontColor(activity?.isDarkTheme() == true)
if (ColorHelper.getFontColor(requireActivity().isDarkTheme())
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
)
widget_shape_background?.setImageDrawable(
BitmapHelper.getTintedDrawable(
val wallpaperDrawable = BitmapHelper.getTintedDrawable(
requireContext(),
R.drawable.card_background,
ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true)
ColorHelper.getBackgroundColor(requireActivity().isDarkTheme())
)
)
withContext(Dispatchers.Main) {
binding.preview.setCardBackgroundColor(bgColor)
binding.widgetDetail.widgetShapeBackground.setImageDrawable(wallpaperDrawable)
}
}
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.generateWidgetView(requireContext(), typeface)
val generatedView = MainWidget.getWidgetView(requireContext(), typeface).root
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview?.measure(0, 0)
binding.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
}
val bitmap = BitmapHelper.getBitmapFromView(
generatedView,
if (binding.preview.width > 0) binding.preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
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 {
binding.widgetDetail.bitmapContainer.apply {
setImageBitmap(bitmap)
scaleX = 0.9f
scaleY = 0.9f
}
widget?.animate()?.alpha(1f)?.start()
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.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()
}
}
showErrorBadge()
}
private fun subscribeUi(viewModel: MainViewModel) {
viewModel.showWallpaper.observe(viewLifecycleOwner, Observer {
activity?.let { act ->
val wallpaper = act.getCurrentWallpaper()
widget_bg.setImageDrawable(if (it) wallpaper else null)
if (wallpaper != null) {
widget_bg.layoutParams =
(widget_bg.layoutParams as ViewGroup.MarginLayoutParams).apply {
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
val metrics = DisplayMetrics()
act.windowManager.defaultDisplay.getMetrics(metrics)
// Timezones
if (Preferences.altTimezoneId != "" && Preferences.altTimezoneLabel != "") {
// Clock
binding.widgetDetail.altTimezoneTime.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneTimeAmPm.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneLabel.text = Preferences.altTimezoneLabel
binding.widgetDetail.altTimezoneTime.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTimeAmPm.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneLabel.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTime.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 3
)
binding.widgetDetail.altTimezoneTimeAmPm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.altTimezoneLabel.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.timezonesContainer.isVisible = true
} else {
binding.widgetDetail.timezonesContainer.isVisible = false
}
val dimensions: Pair<Int, Int> = if (wallpaper.intrinsicWidth >= wallpaper.intrinsicHeight) {
metrics.heightPixels to (wallpaper.intrinsicWidth) * metrics.heightPixels / (wallpaper.intrinsicHeight)
} else {
metrics.widthPixels to (wallpaper.intrinsicHeight) * metrics.widthPixels / (wallpaper.intrinsicWidth)
}
// Clock bottom margin
binding.widgetDetail.clockBottomMarginNone.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.rawValue
binding.widgetDetail.clockBottomMarginSmall.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.rawValue
binding.widgetDetail.clockBottomMarginMedium.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.rawValue
binding.widgetDetail.clockBottomMarginLarge.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.rawValue
setMargins(
if (dimensions.first >= dimensions.second) (-80).toPixel(requireContext()) else 0,
(-80).toPixel(requireContext()), 0, 0
)
// Align
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
gravity = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) Gravity.CENTER_HORIZONTAL else Gravity.NO_GRAVITY
}
}
width = dimensions.first
height = dimensions.second
private fun updateClockVisibility(showClock: Boolean) {
binding.widgetDetail.timeContainer.clearAnimation()
binding.widgetDetail.time.clearAnimation()
updatePreviewVisibility()
if (showClock) {
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
binding.widgetDetail.timeContainer.measure(0, 0)
}
if ((Preferences.showClock && binding.widgetDetail.time.alpha != 1f) || (!Preferences.showClock && binding.widgetDetail.time.alpha != 0f)) {
val initialHeight = binding.widgetDetail.timeContainer.measuredHeight
ValueAnimator.ofFloat(
if (showClock) 0f else 1f,
if (showClock) 1f else 0f
).apply {
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Float
binding.widgetDetail.timeContainer.layoutParams =
binding.widgetDetail.timeContainer.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
binding.widgetDetail.time.alpha = animatedValue
}
}
})
logo.setOnClickListener {
// startActivity(Intent(this, SupportDevActivity::class.java))
}
action_settings.setOnClickListener {
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment)
}.start()
}
}
private fun showErrorBadge() {
// 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
tabs?.getTabAt(2)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = if (Preferences.showWeather) {
(WeatherHelper.isKeyRequired() && WeatherHelper.getApiKey() == "")
|| (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION
) != true)
|| (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-")
} else {
false
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 = 300L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams
layoutParams.height = animatedValue
binding.preview.layoutParams = layoutParams
}
}.start()
}
// Glance error indicator
tabs?.getTabAt(4)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = ((Preferences.showMusic || Preferences.showNotifications) && !ActiveNotificationsHelper.checkNotificationAccess(requireContext())) ||
(Preferences.showDailySteps && !(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION))) ||
(AlarmHelper.isAlarmProbablyWrong(requireContext()))
}
override fun onResume() {
super.onResume()
Preferences.preferences.registerOnSharedPreferenceChangeListener(this)
EventBus.getDefault().register(this)
showErrorBadge()
updateUI()
}
override fun onPause() {
Preferences.preferences.unregisterOnSharedPreferenceChangeListener(this)
EventBus.getDefault().unregister(this)
super.onPause()
}
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 ChangeTabEvent(val page: Int)
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: UpdateUiMessageEvent?) {
fun onUpdateUiEvent(ignore: UpdateUiMessageEvent?) {
delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
delay(300)
withContext(Dispatchers.Main) {
updateUI()
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onChangeTabEvent(ignore: ChangeTabEvent) {
val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
navHost?.navController?.navigateUp()
}
}

View File

@ -4,7 +4,6 @@ import android.Manifest
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
@ -15,6 +14,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import androidx.transition.TransitionInflater
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
@ -24,19 +24,20 @@ import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.BuildConfig
import com.tommasoberlose.anotherwidget.R
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.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.MediaPlayerHelper
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.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.launch
@ -48,20 +49,22 @@ class SettingsFragment : Fragment() {
}
private lateinit var viewModel: MainViewModel
private lateinit var binding: FragmentAppSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
// sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View {
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.viewModel = viewModel
@ -74,25 +77,29 @@ class SettingsFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
action_back.setOnClickListener {
binding.actionBack.setOnSingleClickListener {
Navigation.findNavController(it).popBackStack()
}
show_widget_preview_toggle.isChecked = Preferences.showPreview
show_wallpaper_toggle.isChecked = Preferences.showWallpaper
binding.showWidgetPreviewToggle.setCheckedImmediatelyNoEvent(Preferences.showPreview)
binding.showWallpaperToggle.setCheckedImmediatelyNoEvent(Preferences.showWallpaper)
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(
viewModel: MainViewModel
viewModel: MainViewModel,
) {
viewModel.darkThemePreference.observe(viewLifecycleOwner, Observer {
viewModel.darkThemePreference.observe(viewLifecycleOwner) {
AppCompatDelegate.setDefaultNightMode(it)
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_YES -> getString(R.string.settings_subtitle_dark_theme_dark)
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY -> getString(R.string.settings_subtitle_dark_theme_by_battery_saver)
@ -100,43 +107,45 @@ class SettingsFragment : Fragment() {
else -> ""
}
}
})
}
viewModel.installedIntegrations.observe(viewLifecycleOwner, Observer {
integrations_count_label?.text = getString(R.string.label_count_installed_integrations).format(it)
})
viewModel.installedIntegrations.observe(viewLifecycleOwner) {
binding.integrationsCountLabel.text =
getString(R.string.label_count_installed_integrations).format(
it)
}
viewModel.showPreview.observe(viewLifecycleOwner, Observer {
viewModel.showPreview.observe(viewLifecycleOwner) {
maintainScrollPosition {
show_widget_preview_label?.text =
binding.showWidgetPreviewLabel.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
}
viewModel.showWallpaper.observe(viewLifecycleOwner, Observer {
viewModel.showWallpaper.observe(viewLifecycleOwner) {
maintainScrollPosition {
show_wallpaper_label?.text =
if (it && activity?.checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == true) getString(
binding.showWallpaperLabel.text =
if (it && requireActivity().checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) getString(
R.string.settings_visible
) else getString(R.string.settings_not_visible)
}
})
}
}
private fun setupListener() {
action_show_widget_preview.setOnClickListener {
show_widget_preview_toggle.isChecked = !show_widget_preview_toggle.isChecked
binding.actionShowWidgetPreview.setOnClickListener {
binding.showWidgetPreviewToggle.isChecked = !binding.showWidgetPreviewToggle.isChecked
}
show_widget_preview_toggle.setOnCheckedChangeListener { _, isChecked ->
binding.showWidgetPreviewToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showPreview = isChecked
}
action_show_wallpaper.setOnClickListener {
show_wallpaper_toggle.isChecked = !show_wallpaper_toggle.isChecked
binding.actionShowWallpaper.setOnClickListener {
binding.showWallpaperToggle.isChecked = !binding.showWallpaperToggle.isChecked
}
show_wallpaper_toggle.setOnCheckedChangeListener { _, isChecked ->
binding.showWallpaperToggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
requirePermission()
} else {
@ -144,13 +153,14 @@ class SettingsFragment : Fragment() {
}
}
action_integrations.setOnClickListener {
binding.actionIntegrations.setOnClickListener {
startActivity(Intent(requireContext(), IntegrationsActivity::class.java))
}
action_change_theme.setOnClickListener {
binding.actionChangeTheme.setOnClickListener {
maintainScrollPosition {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_theme_title))
BottomSheetMenu<Int>(requireContext(),
header = getString(R.string.settings_theme_title))
.setSelectedValue(Preferences.darkThemePreference)
.addItem(
getString(R.string.settings_subtitle_dark_theme_light),
@ -170,62 +180,74 @@ class SettingsFragment : Fragment() {
}
}
action_translate.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml")
binding.actionTranslate.setOnClickListener {
requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml")
}
action_website.setOnClickListener {
activity?.openURI("http://tommasoberlose.com/")
binding.actionWebsite.setOnClickListener {
requireActivity().openURI("http://tommasoberlose.com/")
}
action_feedback.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/issues")
binding.actionFeedback.setOnClickListener {
requireActivity().openURI("https://github.com/tommasoberlose/another-widget/issues")
}
action_privacy_policy.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md")
binding.actionPrivacyPolicy.setOnClickListener {
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))
}
action_refresh_widget.setOnClickListener {
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
binding.actionRefreshWidget.setOnClickListener {
binding.actionRefreshIcon
.animate()
.rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
.withEndAction {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
}
}
.start()
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
binding.scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
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() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE
).withListener(object: MultiplePermissionsListener {
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()) {
Preferences.showWallpaper = true
} else {
show_wallpaper_toggle?.isChecked = false
binding.showWallpaperToggle.setCheckedNoEvent(false)
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
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.

View File

@ -1,329 +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 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.showWeatherWarning.observe(viewLifecycleOwner, Observer {
weather_warning?.isVisible = it
checkLocationPermission()
})
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()
})
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 = false
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() {
weather_provider_error.isVisible = Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
weather_provider_error?.text = Preferences.weatherProviderError
weather_provider_location_error.isVisible = Preferences.weatherProviderLocationError != ""
weather_provider_location_error?.text = Preferences.weatherProviderLocationError
}
private fun setupListener() {
action_hide_weather_warning.setOnClickListener {
Preferences.showWeatherWarning = false
}
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,282 @@
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)
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.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.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,262 @@
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.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.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
)
.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,325 @@
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.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 {
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()
}
binding.actionSecondTextSize.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_second_text_size)).setSelectedValue(
Preferences.textSecondSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textSecondSize = value
}.show()
}
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,175 @@
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 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.helpers.ColorHelper.isColor
import com.tommasoberlose.anotherwidget.utils.isMetric
class MainViewModel : ViewModel() {
class MainViewModel(context: Application) : AndroidViewModel(context) {
// General Settings
val textGlobalColor = Preferences.asLiveData(Preferences::textGlobalColor)
val textGlobalAlpha = Preferences.asLiveData(Preferences::textGlobalAlpha)
val textSecondaryColor = Preferences.asLiveData(Preferences::textSecondaryColor)
val textSecondaryAlpha = Preferences.asLiveData(Preferences::textSecondaryAlpha)
val backgroundCardColor = Preferences.asLiveData(Preferences::backgroundCardColor)
val backgroundCardAlpha = Preferences.asLiveData(Preferences::backgroundCardAlpha)
val textGlobalColorDark = Preferences.asLiveData(Preferences::textGlobalColorDark)
val textGlobalAlphaDark = Preferences.asLiveData(Preferences::textGlobalAlphaDark)
val textSecondaryColorDark = Preferences.asLiveData(Preferences::textSecondaryColorDark)
val textSecondaryAlphaDark = Preferences.asLiveData(Preferences::textSecondaryAlphaDark)
val backgroundCardColorDark = Preferences.asLiveData(Preferences::backgroundCardColorDark)
val backgroundCardAlphaDark = Preferences.asLiveData(Preferences::backgroundCardAlphaDark)
val textGlobalColor = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::textGlobalColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalAlphaDark)) { value = true }
}
val textSecondaryColor = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::textSecondaryColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryColorDark)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryAlphaDark)) { value = true }
}
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 textSecondSize = Preferences.asLiveData(Preferences::textSecondSize)
val textShadow = Preferences.asLiveData(Preferences::textShadow)
val textShadowDark = Preferences.asLiveData(Preferences::textShadowDark)
val customFont = Preferences.asLiveData(Preferences::customFont)
val customFontFile = Preferences.asLiveData(Preferences::customFontFile)
val customFontName = Preferences.asLiveData(Preferences::customFontName)
val customFontVariant = Preferences.asLiveData(Preferences::customFontVariant)
val font = MediatorLiveData<Boolean>().apply {
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 }
}
val secondRowInformation = Preferences.asLiveData(Preferences::secondRowInformation)
val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
val widgetAlign = Preferences.asLiveData(Preferences::widgetAlign)
// Calendar Settings
val showEvents = Preferences.asLiveData(Preferences::showEvents)
val calendarAllDay = Preferences.asLiveData(Preferences::calendarAllDay)
val showUntil = Preferences.asLiveData(Preferences::showUntil)
val showDiffTime = Preferences.asLiveData(Preferences::showDiffTime)
val showDeclinedEvents = Preferences.asLiveData(Preferences::showDeclinedEvents)
val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent)
val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails)
val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName)
val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency)
val showOnlyBusyEvents = Preferences.asLiveData(Preferences::showOnlyBusyEvents)
val dateFormat = Preferences.asLiveData(Preferences::dateFormat)
// Clock Settings
val showClock = Preferences.asLiveData(Preferences::showClock)
val clockTextSize = Preferences.asLiveData(Preferences::clockTextSize)
val clockTextColor = Preferences.asLiveData(Preferences::clockTextColor)
val clockTextAlpha = Preferences.asLiveData(Preferences::clockTextAlpha)
val clockTextColorDark = Preferences.asLiveData(Preferences::clockTextColorDark)
val clockTextAlphaDark = Preferences.asLiveData(Preferences::clockTextAlphaDark)
val altTimezoneLabel = Preferences.asLiveData(Preferences::altTimezoneLabel)
val clockTextColor = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
}
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 clockAppName = Preferences.asLiveData(Preferences::clockAppName)
val dateFormat = Preferences.asLiveData(Preferences::dateFormat)
val clockBottomMargin = Preferences.asLiveData(Preferences::clockBottomMargin)
val showBigClockWarning = Preferences.asLiveData(Preferences::showBigClockWarning)
// Weather Settings
val showWeather = Preferences.asLiveData(Preferences::showWeather)
val weatherTempUnit = Preferences.asLiveData(Preferences::weatherTempUnit)
val weatherRefreshPeriod = Preferences.asLiveData(Preferences::weatherRefreshPeriod)
val weatherAppName = Preferences.asLiveData(Preferences::weatherAppName)
val weatherProviderApi = Preferences.asLiveData(Preferences::weatherProviderApiOpen)
val customLocationAdd = Preferences.asLiveData(Preferences::customLocationAdd)
val showWeatherWarning = Preferences.asLiveData(Preferences::showWeatherWarning)
val weatherIconPack = Preferences.asLiveData(Preferences::weatherIconPack)
val weatherProvider = Preferences.asLiveData(Preferences::weatherProvider)
val weatherProviderError = Preferences.asLiveData(Preferences::weatherProviderError)
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
val darkThemePreference = Preferences.asLiveData(Preferences::darkThemePreference)
val showWallpaper = Preferences.asLiveData(Preferences::showWallpaper)
val showPreview = Preferences.asLiveData(Preferences::showPreview)
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::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.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.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.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.util.Log
import androidx.lifecycle.*
import kotlinx.coroutines.Dispatchers
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 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.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.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.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.util.Log
import androidx.lifecycle.*
import com.chibatching.kotpref.livedata.asLiveData
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 androidx.lifecycle.AndroidViewModel

View File

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

View File

@ -7,33 +7,36 @@ import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.google.gson.Gson
import androidx.viewbinding.ViewBinding
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.databinding.TheWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.*
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlinx.android.synthetic.main.the_widget.view.*
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -94,870 +97,19 @@ class MainWidget : AppWidgetProvider() {
val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId)
WidgetHelper.runWithCustomTypeface(context) {
generateWidgetView(context, appWidgetId, appWidgetManager, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
val views = when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
else -> StandardWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
private fun generateWidgetView(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager, w: Int, typeface: Typeface? = null) {
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)
fun getWidgetView(context: Context, typeface: Typeface?): ViewBinding {
return when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidgetView(typeface)
else -> StandardWidget(context).generateWidgetView(typeface)
}
// Clock
views = updateClockView(context, views, appWidgetId)
// Setup listener
try {
val generatedView = generateWidgetView(context, typeface)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedView, width = w)
)
views = updateCalendarView(context, generatedView, views, appWidgetId)
views = updateWeatherView(context, generatedView, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
private fun updateCalendarView(context: Context, v: View, views: RemoteViews, widgetID: Int): RemoteViews {
val eventRepository = EventRepository(context)
try {
views.setImageViewBitmap(
R.id.empty_date_rect,
BitmapHelper.getBitmapFromView(v.empty_date, draw = false)
)
views.setViewVisibility(R.id.empty_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.second_row_rect, View.GONE)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.empty_date_rect, calPIntent)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(v.action_next, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setImageViewBitmap(
R.id.action_previous_rect,
BitmapHelper.getBitmapFromView(v.action_previous, draw = false)
)
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
)
)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
views.setViewVisibility(R.id.action_previous_rect, View.GONE)
}
val pIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, pIntent)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, pIntent)
if (Preferences.showDiffTime && Calendar.getInstance().timeInMillis < (nextEvent.startDate - 1000 * 60 * 60)) {
views.setImageViewBitmap(
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
v.next_event_difference_time,
draw = false
)
)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
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.second_row_rect, mapIntent)
} else {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(v.next_event, draw = false)
)
views.setImageViewBitmap(
R.id.second_row_rect,
BitmapHelper.getBitmapFromView(v.second_row, draw = false)
)
views.setViewVisibility(R.id.second_row_rect, View.VISIBLE)
views.setViewVisibility(R.id.empty_layout_rect, View.GONE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(
R.id.second_row_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
)
} else if (GlanceProviderHelper.showGlanceProviders(context) && v.calendar_layout.isVisible) {
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.second_row_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.second_row_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.second_row_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.second_row_rect, fitIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
val remotePackageContext = context.createPackageContext(Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext, Preferences.lastNotificationIcon)
val notificationIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.second_row_rect,
notificationIntent
)
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
}
}
if (showSomething) {
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(v.next_event, draw = false)
)
views.setImageViewBitmap(
R.id.second_row_rect,
BitmapHelper.getBitmapFromView(v.second_row, draw = false)
)
views.setViewVisibility(R.id.second_row_rect, View.VISIBLE)
views.setViewVisibility(R.id.empty_layout_rect, View.GONE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setOnClickPendingIntent(R.id.next_event_rect, calPIntent)
views.setViewVisibility(
R.id.second_row_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
)
}
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
}
private fun updateWeatherView(context: Context, v: View, views: RemoteViews, widgetID: Int): RemoteViews {
try {
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.special_weather_rect, View.VISIBLE)
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.calendar_weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.special_weather_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(v.weather, draw = false)
)
views.setImageViewBitmap(
R.id.calendar_weather_rect,
BitmapHelper.getBitmapFromView(v.calendar_weather, draw = false)
)
views.setImageViewBitmap(
R.id.special_weather_rect,
BitmapHelper.getBitmapFromView(v.calendar_weather, draw = false)
)
if (GlanceProviderHelper.showGlanceProviders(context)) {
views.setViewVisibility(R.id.calendar_weather_rect, View.GONE)
} else {
views.setViewVisibility(R.id.special_weather_rect, View.GONE)
}
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.calendar_weather_rect, View.GONE)
views.setViewVisibility(R.id.special_weather_rect, View.GONE)
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateClockView(context: Context, 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)
} 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.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_small,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_medium,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.clock_bottom_margin_large,
if (Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value) View.VISIBLE else View.GONE
)
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(context: Context, typeface: Typeface? = null): View {
val eventRepository = EventRepository(context)
val v = View.inflate(context, R.layout.the_widget, null)
v.loader.isVisible = false
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
v.empty_layout.visibility = View.VISIBLE
v.calendar_layout.visibility = View.GONE
v.next_event_difference_time.visibility = View.GONE
v.action_next.isVisible = false
v.action_previous.isVisible = false
v.empty_date.text = DateHelper.getDateText(context, now)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null) {
// Multiple counter
v.action_next.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.action_previous.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.next_event.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
v.next_event_difference_time.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
v.next_event_difference_time.visibility = View.VISIBLE
} else {
v.next_event_difference_time.visibility = View.GONE
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place
)
)
v.next_event_date.text = nextEvent.address
} else {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today
)
)
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)
)
}
v.next_event_date.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
v.next_event_date.text =
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
v.second_row_icon.isVisible = true
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note
)
)
v.next_event_date.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm
)
)
v.next_event_date.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
v.second_row_icon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel != 100) {
v.next_event_date.text = context.getString(R.string.charging)
} else {
v.next_event_date.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
v.second_row_icon.isVisible = false
v.next_event_date.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
v.second_row_icon.isVisible = false
v.next_event_date.text = Preferences.customNotes
v.next_event_date.gravity
v.next_event_date.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
v.second_row_icon.isVisible = false
v.next_event_date.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
val remotePackageContext = context.createPackageContext(Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext, Preferences.lastNotificationIcon)
v.second_row_icon.isVisible = true
v.second_row_icon.setImageDrawable(icon)
v.next_event_date.text = Preferences.lastNotificationTitle
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
}
}
if (showSomething) {
v.next_event.text = DateHelper.getDateText(context, now)
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else {
v.second_row_icon.isVisible = false
}
}
// Color
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.divider3,
v.special_temp
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.value) {
listOf<ImageView>(v.action_next, v.action_previous)
} else {
listOf<ImageView>(
v.action_next,
v.action_previous,
v.empty_weather_icon,
v.special_weather_icon
)
}.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>(v.next_event_date, v.divider2, v.calendar_temp).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.value) {
listOf<ImageView>(v.second_row_icon)
} else {
listOf<ImageView>(v.second_row_icon, v.weather_icon)
}.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>>(
v.empty_date to Preferences.textMainSize,
v.divider1 to (Preferences.textMainSize - 2),
v.temp to Preferences.textMainSize,
v.next_event to Preferences.textMainSize,
v.next_event_difference_time to Preferences.textMainSize,
v.next_event_date to Preferences.textSecondSize,
v.divider2 to (Preferences.textSecondSize - 2),
v.calendar_temp to Preferences.textSecondSize,
v.divider3 to (Preferences.textMainSize - 2),
v.special_temp to Preferences.textMainSize
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
}
// Icons scale
v.second_row_icon.scaleX = Preferences.textSecondSize / 18f
v.second_row_icon.scaleY = Preferences.textSecondSize / 18f
v.weather_icon.scaleX = Preferences.textSecondSize / 14f
v.weather_icon.scaleY = Preferences.textSecondSize / 14f
v.empty_weather_icon.scaleX = Preferences.textMainSize / 18f
v.empty_weather_icon.scaleY = Preferences.textMainSize / 18f
v.action_next.scaleX = Preferences.textMainSize / 28f
v.action_next.scaleY = Preferences.textMainSize / 28f
v.action_previous.scaleX = Preferences.textMainSize / 28f
v.action_previous.scaleY = Preferences.textMainSize / 28f
v.special_weather_icon.scaleX = Preferences.textMainSize / 20f
v.special_weather_icon.scaleY = Preferences.textMainSize / 20f
// 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>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// 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>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.typeface = typeface
}
}
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
v.weather.visibility = View.VISIBLE
v.calendar_weather.visibility = View.VISIBLE
v.special_weather.visibility = View.VISIBLE
val currentTemp = String.format(
Locale.getDefault(),
"%d °%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
v.weather_icon.visibility = View.GONE
v.empty_weather_icon.visibility = View.GONE
v.special_weather_icon.visibility = View.GONE
} else {
v.weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.empty_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.special_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.weather_icon.visibility = View.VISIBLE
v.empty_weather_icon.visibility = View.VISIBLE
v.special_weather_icon.visibility = View.VISIBLE
}
v.temp.text = currentTemp
v.calendar_temp.text = currentTemp
v.special_temp.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
v.calendar_weather.visibility = View.GONE
} else {
v.special_weather.visibility = View.GONE
}
} else {
v.weather.visibility = View.GONE
v.calendar_weather.visibility = View.GONE
v.special_weather.visibility = View.GONE
}
// Dividers
arrayOf(v.divider1, v.divider2, v.divider3).forEach {
it.isVisible = Preferences.showDividers
}
eventRepository.close()
return v
}
}
}

View File

@ -0,0 +1,930 @@
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.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)
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 {
val eventRepository = EventRepository(context)
try {
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false)
)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
// Action previous event
views.setImageViewBitmap(
R.id.action_previous_rect,
BitmapHelper.getBitmapFromView(bindingView.actionPrevious, draw = false)
)
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
)
)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false)
)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.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)
)
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)
}
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding {
val eventRepository = EventRepository(context)
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 nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.actionPrevious.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
bindingView.nextEventDifferenceTime.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.isVisible = true
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place_24
)
)
bindingView.subLineText.text = nextEvent.address
} else {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
if (!nextEvent.allDay) {
val startHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.startDate)
val endHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.endDate)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
val endCal = Calendar.getInstance()
endCal.timeInMillis = nextEvent.endDate
if (startCal.get(Calendar.HOUR_OF_DAY) > endCal.get(Calendar.HOUR_OF_DAY)) {
dayDiff++
} else if (startCal.get(Calendar.HOUR_OF_DAY) == endCal.get(Calendar.HOUR_OF_DAY) && startCal.get(
Calendar.MINUTE
) > endCal.get(Calendar.MINUTE)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
if (nextEvent.startDate != nextEvent.endDate) {
bindingView.subLineText.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
bindingView.subLineText.text =
String.format("%s", startHour)
}
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateUtils.formatDateTime(context, now.timeInMillis, flags)
} else {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = true
bindingView.subLineTopMarginSmall.visibility =
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
}
}
eventRepository.close()
return bindingView
}
}

View File

@ -1,12 +1,10 @@
package com.tommasoberlose.anotherwidget.utils
import android.animation.*
import android.content.pm.PackageManager
import android.view.Gravity
import android.view.View
import android.view.ViewAnimationUtils
import android.widget.Toast
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Activity
import android.app.WallpaperManager
import android.content.*
@ -23,10 +21,14 @@ import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.animation.Animation
import android.view.animation.Transformation
import android.widget.LinearLayout
import android.view.animation.AlphaAnimation
import android.widget.RelativeLayout
import androidx.annotation.UiThread
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.core.animation.addListener
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.OnSingleClickListener
import java.util.*
@ -68,61 +70,78 @@ fun View.reveal(initialX: Int? = null, initialY: Int? = null) {
}
fun View.expand() {
if (visibility != View.VISIBLE) {
measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val targetHeight = measuredHeight
fun View.expand(duration: Long = 500L) {
clearAnimation()
try {
val animator = (tag as ValueAnimator)
animator.removeAllListeners()
animator.cancel()
} catch (ex: java.lang.Exception) {}
layoutParams.height = 0
visibility = View.VISIBLE
val a = object : Animation() {
protected override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
layoutParams.height = if (interpolatedTime == 1f)
LinearLayout.LayoutParams.WRAP_CONTENT
else
(targetHeight * interpolatedTime).toInt()
translationY = 0f
requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = 500L
startAnimation(a)
layoutParams = layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
measure(0, 0)
val initialHeight = measuredHeight
val anim = ValueAnimator.ofFloat(
alpha,
1f
).apply {
this.duration = duration
addUpdateListener {
val animatedValue = animatedValue as Float
layoutParams = layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
translationY = (initialHeight * animatedValue - initialHeight)
alpha = animatedValue
}
addListener(
onStart = {
isVisible = true
}
)
}
tag = anim
anim.start()
}
fun View.collapse(duration: Long = 500L) {
if (visibility != View.GONE) {
val initialHeight = measuredHeight
val a = object : Animation() {
protected override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
clearAnimation()
try {
val animator = (tag as ValueAnimator)
animator.removeAllListeners()
animator.cancel()
} catch (ex: java.lang.Exception) {}
val initialHeight = measuredHeight
val anim = ValueAnimator.ofFloat(
alpha,
0f
).apply {
this.duration = duration
addUpdateListener {
val animatedValue = animatedValue as Float
layoutParams = layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
translationY = (initialHeight * animatedValue - initialHeight)
alpha = animatedValue
}
a.duration = duration //(initialHeight / v.context.resources.displayMetrics.density).toLong()
startAnimation(a)
addListener(
onEnd = {
isVisible = false
}
)
}
tag = anim
anim.start()
}
fun Context.openURI(url: String) {
try {
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()
customTabsIntent.launchUrl(this, Uri.parse(url))
} catch (e: Exception) {
@ -230,4 +249,19 @@ fun Intent.isDefaultSet(context: Context): Boolean {
} catch (ex: java.lang.Exception) {
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))
}

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.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

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