Compare commits

..

380 Commits

Author SHA1 Message Date
cadc881ad8 Replaced google weather url with yandex 2023-03-24 10:50:32 +03:00
f25ce937ce Fixed release linting errors by removing unused resources 2023-03-24 06:18:50 +03:00
564962dc9c Removed google shit 2023-03-24 06:18:16 +03:00
azuo
f893748941 Prevent the time until the event from being split onto separate lines 2023-03-01 16:19:18 +08:00
azuo
64ff404eac Reset the next event only if necessary 2022-02-20 18:27:28 +08:00
azuo
b95a9e8e7f Keep the next event after updating the event list, if it still exists 2022-02-19 13:46:33 +08:00
azuo
1667d9c22c Show the correct date for all-day events in the coming year 2021-12-29 23:45:32 +08:00
azuo
fb3f28d035 Make weather updates more reliable 2021-10-15 14:42:38 +08:00
azuo
d8e204c5d9 Optimize background Workers, remove redundant JobServices
1. Adapt the location callback in WeatherWorker to coroutines
2. Merge EventListenerJob into UpdateCalendarWorker
3. Remove BatteryListenerJob, as it's impossible to use Jobs to detect battery status
2021-10-07 16:03:07 +08:00
azuo
388653f62b Meet some mandatory requirements for migrating to Android 12
1. Replace foreground services with Workers
2. Support approximate location
3. Fallback to inexact alarms if the SCHEDULE_EXACT_ALARM permission is revoked
4. Specify the mutability of each PendingIntent
5. Explicitly declare the android:exported attribute for app components that use intent filters
2021-10-03 16:34:56 +08:00
azuo
5763a18421 Optimize notification handling, fix activity detection and greetings. 2021-10-01 17:05:43 +08:00
azuo
5dcf0afe02 Correct merge mistake: viewModel.showPreview.observe invoked twice 2021-09-30 22:06:38 +08:00
azuo
94b1eec757 Revert: Add ACCESS_BACKGROUND_LOCATION permission. 2021-09-30 20:06:14 +08:00
azuo
c5b41d0886
Merge branch 'develop' into patch-develop 2021-09-30 16:56:29 +08:00
Tommaso Berlose
4e5bf62e23 UI updates 2021-09-29 14:35:15 +02:00
azuo
77864cbef4 Suppress unused parameter warnings. 2021-09-27 00:02:50 +08:00
azuo
32c580bac7 Merge branch 'patch-1' into patch-develop 2021-09-26 22:29:09 +08:00
azuo
85fa0cae11 Add weather providers HERE.com and AccuWeather.com; fix Weather.gov. 2021-09-26 22:28:06 +08:00
azuo
05f2a745c2 Merge branch 'patch-1' into patch-develop
# Conflicts:
#	app/src/main/java/com/tommasoberlose/anotherwidget/helpers/WeatherHelper.kt
2021-09-25 11:34:02 +08:00
azuo
ef2e89b6ff Correct weather info for weatherbit.io, weather.gov and yr.no. 2021-09-25 11:31:53 +08:00
azuo
a306d92282 Make text shadows more visible. 2021-09-25 11:08:10 +08:00
azuo
da9fc362af Merge branch 'patch-3' into patch-develop 2021-09-24 10:45:07 +08:00
azuo
ff83cfd953 Update translation. 2021-09-24 10:44:05 +08:00
azuo
5d9dcd9701 Replace Realm with Room. 2021-09-23 00:23:42 +08:00
azuo
183901534c Correct the widget layout.
Adjust the layout carefully, so that remote grid views perfectly overlap the bitmap generated from the binding view, whether left-aligned, right-aligned or centered, and regardless of the size of the widget, text, margins or spacing.

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

I also just noticed that there's no string for the text "Default apps" in the Gestures menu, right above the app selection for calendar, weather and clock. So this text keeps showing up in English on my phone, even though the translation is complete.
2021-05-07 20:30:29 +02:00
Tommaso Berlose
7edb0635a7
Merge pull request #313 from chreddy/patch-5
Update Danish translation
2021-05-07 17:35:29 +02:00
chreddy
d72ddd6d85
Update Danish translation 2021-05-07 17:33:24 +02:00
Tommaso Berlose
5d07cc8d73 Added color copy/paste, better size and color text selection 2021-05-07 17:19:23 +02:00
Tommaso Berlose
1ac53e09a8
Merge pull request #312 from chreddy/patch-6
Updating Danish translation
2021-05-07 15:09:55 +02:00
Tommaso Berlose
3412e044df
Merge pull request #311 from Drumber/translation
Update German strings.xml
2021-05-07 15:09:30 +02:00
chreddy
80023da430
Updating Danish translation 2021-05-07 15:09:17 +02:00
Lars
e2a2d17506 Update German strings.xml 2021-05-07 13:35:38 +02:00
Tommaso Berlose
b93443b736 Added right-aligned widget 2021-05-07 12:21:31 +02:00
Tommaso Berlose
9842ba3ea9 Bugfixes 2021-05-06 17:29:29 +02:00
Tommaso Berlose
75aba66987 Bugfixes 2021-05-05 18:23:01 +02:00
Tommaso Berlose
f325af26f8 Bump version to v2.3.1 2021-05-05 14:54:24 +02:00
Tommaso Berlose
b61fbd193c Added single multiple clock 2021-05-05 14:53:43 +02:00
Tommaso Berlose
03d9446369 Bump version to v2.3.0 2021-05-05 12:44:21 +02:00
Tommaso Berlose
23f94e63c6 Merge branch 'main' of github.com:tommasoberlose/another-widget into develop 2021-05-05 12:13:54 +02:00
Tommaso Berlose
01775d3a3a
Merge pull request #308 from tommasoberlose/widget-align
Widget align and bugfixes
2021-05-05 12:12:55 +02:00
Tommaso Berlose
67abd14bb1 Bugfixes 2021-05-05 12:11:51 +02:00
Tommaso Berlose
854cfac28c Bugfixes 2021-05-05 10:54:09 +02:00
Tommaso Berlose
8fafe591e8
Merge pull request #299 from cccClyde/main
Updated localization for Simplified Chinese.
2021-05-05 10:35:20 +02:00
Tommaso Berlose
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
Tommaso Berlose
974185a89b
Revert "Update strings for ru.xml" 2021-05-05 10:33:19 +02:00
Tommaso Berlose
bbe1497f8b
Merge pull request #305 from Dima-a-galaxy/patch-2
Update strings for ru.xml
2021-05-05 10:32:56 +02:00
Tommaso Berlose
1f22426dec Update align support 2021-05-04 18:32:20 +02:00
Tommaso Berlose
40644f3657 update widget 2021-05-03 23:01:54 +02:00
Dima-a-galaxy
a01c8eff63
Update strings.xml 2021-04-21 10:09:43 +04:00
cccClyde
1ee25bcc89
Updated localization for Simplified Chinese.
Updated localization for Simplified Chinese, now it's 100% completed.
2021-04-02 13:29:18 +08:00
Tommaso Berlose
1bd18ac486 Merge branch 'main' of github.com:tommasoberlose/another-widget into develop 2021-01-24 13:00:24 +01:00
Tommaso Berlose
d24ac198a4
Merge pull request #283 from Lambada10/main
Add Polish Translation
2021-01-24 13:00:07 +01:00
Tommaso Berlose
81578f5f4a Fix #282, bug fixes 2021-01-24 12:48:34 +01:00
Lambada10
8234a87a2a Translations 2021-01-18 14:17:27 +01:00
Lambada10
0774c6bdbe Translations 2021-01-18 12:12:58 +01:00
Tommaso Berlose
57eecd630d Fix #275 2021-01-14 17:41:33 +01:00
Tommaso Berlose
cce86a970c Fix #278 2021-01-14 14:14:00 +01:00
Tommaso Berlose
6ea40a51fe Beta version 2.2.2-beta9 2021-01-12 15:46:48 +01:00
Tommaso Berlose
1721dff3cf Fix #273, fix #271 2021-01-12 15:42:16 +01:00
Tommaso Berlose
c389d50b09 Fix background service error 2021-01-12 09:51:00 +01:00
Tommaso Berlose
0ea55db4b1 Update strings 2021-01-11 15:10:28 +01:00
Tommaso Berlose
3fba50fd2c Fix #265 2021-01-11 09:49:00 +01:00
Tommaso Berlose
06583197c7 Fix widget refresh intent 2021-01-11 09:42:55 +01:00
Tommaso Berlose
5176331e84 Version 2.2.2-beta5 2021-01-11 01:34:38 +01:00
Tommaso Berlose
65f83caeb5
Merge pull request #267 from Moutony/patch-27
French v2.2.2 (120)
2021-01-11 01:33:22 +01:00
Tommaso Berlose
60e8c267bf Version 2.2.2-beta4 2021-01-11 01:32:29 +01:00
Tommaso Berlose
10a3204808 Fix #263 2021-01-11 01:30:31 +01:00
Moutony
98c509ef27
French v2.2.2 (120) 2021-01-10 19:06:20 +01:00
Tommaso Berlose
ab1df499af beta version 2.2.2-beta3 2021-01-10 18:02:44 +01:00
Tommaso Berlose
ed9a4042c8 Fix #259 2021-01-10 17:56:48 +01:00
Tommaso Berlose
a102776214 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2021-01-10 17:21:34 +01:00
Tommaso Berlose
0778ad4df5
Merge pull request #264 from Drumber/translation
Update German strings.xml
2021-01-10 17:21:27 +01:00
Tommaso Berlose
8dce8a74b3 Fix #242 2021-01-10 17:20:52 +01:00
Tommaso Berlose
24bb811f93 Fix update calendar service 2021-01-10 15:54:54 +01:00
Lars
0500e8d8e8 Update strings.xml
Added missing translations
2021-01-10 14:16:19 +01:00
Tommaso Berlose
d2087d094f Beta version 2.2.2-beta1 2021-01-10 12:52:20 +01:00
Tommaso Berlose
34fb35f2ab Fix #262 2021-01-10 12:48:17 +01:00
Tommaso Berlose
108ecdece0 Update transitions 2021-01-10 02:08:15 +01:00
Tommaso Berlose
e3f4995e93 Fix icon selector 2021-01-10 00:41:36 +01:00
Tommaso Berlose
0cdc5a3c6d Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2021-01-09 20:28:03 +01:00
Tommaso Berlose
9f039eec3c
Merge pull request #258 from Moutony/patch-26
Update strings.xml
2021-01-09 20:27:51 +01:00
Tommaso Berlose
e9effbe799 Update animations 2021-01-09 20:26:23 +01:00
Tommaso Berlose
889783bb4e update ui 2021-01-09 18:33:22 +01:00
Tommaso Berlose
d32f680519 Update UI 2021-01-09 00:24:05 +01:00
Tommaso Berlose
c1d14f93bf Improve performances 2021-01-08 17:49:12 +01:00
Tommaso Berlose
b903fff10f UI update 2021-01-08 01:17:48 +01:00
Tommaso Berlose
20c5ce61b4 Update ui 2021-01-07 22:51:45 +01:00
Tommaso Berlose
5bb81772f4 UI update 2021-01-07 18:24:20 +01:00
Tommaso Berlose
526a9ac6ac Change UI 2021-01-07 16:11:10 +01:00
Tommaso Berlose
98db1380b7 Move to kotlin data binding and update main fragoment 2021-01-07 15:09:58 +01:00
Tommaso Berlose
ce9b343e0e Change file names 2021-01-07 12:44:37 +01:00
Moutony
21ec3c3f5d
Update strings.xml 2021-01-06 22:06:43 +01:00
Moutony
88950a84b4
Update strings.xml 2021-01-06 21:58:01 +01:00
Tommaso Berlose
fb853975e0 Update app navigation 2021-01-06 21:31:01 +01:00
Tommaso Berlose
329eee6339 Add new dark icons 2021-01-06 21:11:10 +01:00
Tommaso Berlose
c595168320 Merge develop 2021-01-06 17:33:24 +01:00
Tommaso Berlose
61e3e43fd7 Publish v2.1.1 2021-01-06 17:30:42 +01:00
Tommaso Berlose
1513b96313 Bugfixes, fix #257 2021-01-06 17:26:32 +01:00
Tommaso Berlose
1cc5558d9e Add dark icons 2021-01-06 17:19:36 +01:00
Tommaso Berlose
e12e908496 UI update 2021-01-05 17:53:00 +01:00
Tommaso Berlose
2aed9e3b25 Merge branch 'develop' of github.com:tommasoberlose/another-widget into design-update 2021-01-05 11:08:26 +01:00
Tommaso Berlose
536ed64d41 Default temp unit and weather provider based on locale 2021-01-05 11:08:13 +01:00
Tommaso Berlose
6b6ec633ee Update main design 2021-01-05 10:46:12 +01:00
Tommaso Berlose
5b2d245e80 Merge develop 2021-01-04 12:07:59 +01:00
Tommaso Berlose
0aec34dcd2 Bugfixes 2021-01-04 12:04:25 +01:00
Tommaso Berlose
b2a9b9fbb3 Fix #255 2021-01-03 17:51:31 +01:00
Tommaso Berlose
768f04825e Building 2021-01-03 17:25:00 +01:00
Tommaso Berlose
80ee877bf2
Merge pull request #252 from devindang/master
Update new strings for Chinese Simplified
2021-01-03 17:24:18 +01:00
Tommaso Berlose
ac7839ecdc
Merge pull request #244 from Moutony/patch-25
Update French Greetings
2021-01-03 17:24:11 +01:00
Tommaso Berlose
66d0214cd9 Move the location updates to a foreground service and fix multiple bugs 2021-01-03 17:23:22 +01:00
devindang
e069b8f6ab greetings 2020-11-27 23:47:13 +08:00
devin
8a681f0cd7 Update New Strings 2020-11-25 20:46:02 +08:00
Moutony
92158ec5f2
Update strings.xml 2020-11-22 12:21:56 +01:00
Tommaso Berlose
ebb37f21ed Remove useless icons 2020-10-30 19:26:01 +01:00
Moutony
2a389cb422
Update strings.xml 2020-10-29 12:12:27 +01:00
Moutony
4504a0617e
Update Fitness in Glance 2020-10-28 14:58:22 +01:00
Moutony
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
Tommaso Berlose
a7294edfd4 Fix notification listener 2020-10-27 18:28:21 +01:00
Tommaso Berlose
49ca17803e Update translations 2020-10-27 18:21:55 +01:00
Tommaso Berlose
97ab72081d Update the UI 2020-10-27 18:13:27 +01:00
Tommaso Berlose
e6fee0dfe1 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-27 18:13:15 +01:00
Tommaso Berlose
e0c4f24c43
Merge pull request #239 from Vgbhieel/master
Added Portuguese translation (values-pt)
2020-10-27 18:13:02 +01:00
Vitor Nascimento
2a7d0f171b
Create strings,xml for portuguese values
translated all strings to portuguese
2020-10-23 00:55:39 -03:00
Tommaso Berlose
00dcfc3149 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-20 10:39:19 +02:00
Tommaso Berlose
eb11b603aa
Merge pull request #235 from Moutony/patch-24
Update French 2
2020-10-20 10:39:01 +02:00
Tommaso Berlose
4d2f624448 Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-20 10:38:47 +02:00
Tommaso Berlose
7581af4dd2 Merge branch 'english-strings' 2020-10-20 10:38:19 +02:00
Tommaso Berlose
0325af6582 Fix uppercase labels 2020-10-20 10:35:54 +02:00
Tommaso Berlose
4de0413a35 Fix multiple bugs 2020-10-20 10:27:05 +02:00
Moutony
c54a24c889
Update strings.xml 2020-10-19 21:54:51 +02:00
Moutony
98eccd2833
Update strings.xml 2020-10-19 21:54:17 +02:00
Moutony
0119a20765
Update strings.xml 2020-10-19 21:51:12 +02:00
Moutony
a1e54892fd
Update strings.xml 2020-10-19 16:28:38 +02:00
Moutony
17801fd164
Update strings.xml 2020-10-19 16:26:57 +02:00
Tommaso Berlose
e6087b2969
Merge pull request #234 from Moutony/patch-23
Update French
2020-10-19 15:35:06 +02:00
Tommaso Berlose
59b8ba26a2
Merge pull request #232 from d-l-n/master
Added and translated new strings
2020-10-19 15:35:01 +02:00
Tommaso Berlose
f190ee5d15
Merge pull request #231 from Drumber/translation
Update German translation
2020-10-19 15:34:48 +02:00
Moutony
2f266ffcf8
Update strings.xml 2020-10-19 11:07:05 +02:00
Moutony
0e376aba80
Update strings.xml 2020-10-19 11:06:29 +02:00
Moutony
313e4fa92d
Update French 2020-10-19 11:01:21 +02:00
dylan
a8ec754bc4 Added and translated new strings 2020-10-19 01:04:33 -03:00
Lars
f8d1188634 Update strings.xml
Added greetings and notification timeout strings
2020-10-18 18:47:52 +02:00
Moutony
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
Tommaso Berlose
56d95c5559 Update the bundle version 2020-10-18 12:58:13 +02:00
Tommaso Berlose
c0e747a714 Add greetings as glance provider 2020-10-18 12:51:02 +02:00
Tommaso Berlose
16076dc145 Fix #225 2020-10-18 11:21:52 +02:00
Tommaso Berlose
c95f9fb943 Fix #222 2020-10-18 11:20:40 +02:00
Tommaso Berlose
331d5772af Added the shadow to the icons 2020-10-16 18:04:30 +02:00
Tommaso Berlose
e2719b6445 Fix #181 2020-10-16 12:31:32 +02:00
Tommaso Berlose
bd35022f7d Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-16 11:58:38 +02:00
Tommaso Berlose
6d9cb750a7
Merge pull request #220 from Moutony/patch-20
Update strings.xml (English) for syntax
2020-10-16 11:58:27 +02:00
Tommaso Berlose
f85b4c9a6a
Merge pull request #223 from Drumber/translation
Update German translation
2020-10-16 11:58:22 +02:00
Tommaso Berlose
df54a4a79e
Merge pull request #221 from Moutony/patch-21
Update French
2020-10-16 11:58:10 +02:00
Tommaso Berlose
6ea97e7724 Fix the events order 2020-10-16 11:58:02 +02:00
Moutony
d27071739d
Update strings.xml 2020-10-15 22:59:12 +02:00
Lars
738781225f Update strings.xml
Added missing strings and updated some translations
2020-10-15 20:03:53 +02:00
Moutony
12d32f852b
Update French
Shortened some sentences
2020-10-15 19:10:17 +02:00
Moutony
f0baa60363
Update strings.xml 2020-10-15 18:43:35 +02:00
Moutony
785eb39334
Update strings.xml (English) for syntax
<string name="weather_warning">
2020-10-15 18:15:48 +02:00
Tommaso Berlose
d289699d08
Merge pull request #213 from Moutony/patch-17
Update strings.xml (English) for UI optimization
2020-10-15 10:12:58 +02:00
Tommaso Berlose
c68d0a8327
Merge pull request #215 from Moutony/patch-19
Update strings.xml (French)
2020-10-15 10:12:49 +02:00
Tommaso Berlose
de2e223713
Merge pull request #214 from chreddy/patch-3
Updating Danish translation
2020-10-15 10:12:15 +02:00
Tommaso Berlose
6150dd7e22 Fix #217 2020-10-15 10:10:38 +02:00
Moutony
d9ecebe770
Update strings.xml 2020-10-14 22:30:44 +02:00
Moutony
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
Moutony
e301e6e6ec
Update strings.xml
Grammar
2020-10-14 22:11:56 +02:00
Moutony
c884fae362
Update strings.xml
Added (EN) to Legal & Privacy
2020-10-14 20:42:29 +02:00
Moutony
eb78257e52
Update strings.xml
Fixed some old things too.
Update 2 of today.
2020-10-14 20:37:30 +02:00
Moutony
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
chreddy
2403f066a3
Updating Danish translation 2020-10-14 19:50:11 +02:00
Moutony
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
Tommaso Berlose
a1a6f9f607 Fix the privacy policy error 2020-10-14 14:56:37 +02:00
Tommaso Berlose
815a88a079 Release v2.0.15-beta1 2020-10-14 12:56:55 +02:00
Tommaso Berlose
1c1f55e20a Add the app notifications filter 2020-10-14 12:54:39 +02:00
Tommaso Berlose
5259a81cfb Fix #212 2020-10-14 11:47:11 +02:00
Tommaso Berlose
3b963dae1c Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-14 11:42:16 +02:00
Tommaso Berlose
878ddcb05e Update the UI and fix #211 2020-10-14 11:42:03 +02:00
Tommaso Berlose
0ce446f0ef
Merge pull request #209 from Moutony/patch-16
Update string.xml (French)
2020-10-14 10:03:39 +02:00
Tommaso Berlose
c5fefb0e06 Update the glance section 2020-10-14 01:41:43 +02:00
Tommaso Berlose
c5eb5358aa Merge branch 'master' of github.com:tommasoberlose/another-widget into develop 2020-10-14 00:45:37 +02:00
Tommaso Berlose
c4a16224f0 Update the glance support 2020-10-14 00:45:29 +02:00
Tommaso Berlose
122c0627d9
Merge pull request #208 from Moutony/patch-14
Update strings.xml (English/International)
2020-10-14 00:45:16 +02:00
Moutony
6d80ec97a8
Update string.xml (French)
I added new translations from the lastest English string.xml file (13 oct. 2020)
2020-10-14 00:36:47 +02:00
Moutony
1c7df585fe
Update strings.xml
Added translatable="false" to all font thickness names (100 to 900).

UI optimization:
- I shortened some sentenced because they were oddly long: text sometimes goes on a second row (view screenshots).
- I changed the website name style (uppercases) of few weather providers to match the official style.

Line 140: Meteorologisk Institutt is the official name in Norwegian.
2020-10-13 22:48:53 +02:00
Tommaso Berlose
9f4cdc950b Merge 2020-10-13 16:56:07 +02:00
Tommaso Berlose
0a0c5a90a7 Update the glance section 2020-10-13 16:55:42 +02:00
Tommaso Berlose
011517e12d
Merge pull request #206 from chreddy/patch-4
Updated Danish translation
2020-10-13 16:55:19 +02:00
chreddy
fdd82e0f91
Updated Danish translation 2020-10-13 16:40:27 +02:00
Tommaso Berlose
abafe108c6 UI update 2020-10-13 11:39:53 +02:00
Tommaso Berlose
03f08e4f08 Update the settings fragment 2020-10-13 11:38:02 +02:00
Tommaso Berlose
9ecb9b4819 Ui update 2020-10-13 11:32:10 +02:00
Tommaso Berlose
2bb30aae69 Update the add button 2020-10-12 12:09:24 +02:00
Tommaso Berlose
01d219d38c Bugfix 2020-10-12 10:27:58 +02:00
Tommaso Berlose
6c7831d972
Create FUNDING.yml 2020-10-12 10:26:22 +02:00
Tommaso Berlose
4ab12e33c7 Beta release v2.0.14 2020-10-11 22:09:27 +02:00
Tommaso Berlose
364198ef08 Update the providers system 2020-10-11 22:05:20 +02:00
Tommaso Berlose
2c81c7cfd2
Merge pull request #197 from d-l-n/master
Translations fix
2020-10-11 22:04:06 +02:00
Tommaso Berlose
3b723e5a1b
Merge pull request #200 from Drumber/translation
Update German translation
2020-10-11 22:03:45 +02:00
Tommaso Berlose
6a912ee003 Add a few more providers and fix multiple bugs 2020-10-11 21:59:44 +02:00
Tommaso Berlose
1644fb7682 Update the providers 2020-10-06 18:11:53 +02:00
Tommaso Berlose
bd4869540b Connect weather.gov api 2020-10-05 19:07:52 +02:00
Lars
31103303be Update German translation 2020-10-05 18:06:46 +02:00
Tommaso Berlose
e9c0cdd61c Merge 2020-10-05 14:36:56 +02:00
Tommaso Berlose
59e42029e8 Merge and move google sans in the custom font activity 2020-10-05 12:31:49 +02:00
Tommaso Berlose
b2ef2adb2a Update the weather icons 2020-10-05 11:36:33 +02:00
Tommaso Berlose
bab92f9169 Add music players filter, fix #188 2020-10-05 11:09:11 +02:00
Tommaso Berlose
cb480ed4ee Fix the events order, fix #198 2020-10-05 09:43:31 +02:00
Tommaso Berlose
6f83a45865 Merge branch 'master' of github.com:tommasoberlose/another-widget into font 2020-10-05 00:59:47 +02:00
Tommaso Berlose
9e528e1f6f Update the icons 2020-10-05 00:59:37 +02:00
Tommaso Berlose
20d3ae0e32 Bugfix 2020-10-05 00:46:29 +02:00
Tommaso Berlose
ecea1265e7 Show the variant 2020-10-04 21:23:51 +02:00
Tommaso Berlose
c38b7a335c Add the font variant 2020-10-04 21:19:22 +02:00
Tommaso Berlose
6ab8e40d45 Add downloadable fonts 2020-10-04 20:39:52 +02:00
Tommaso Berlose
d14dfee980 Update the provider activity 2020-10-04 18:02:08 +02:00
Tommaso Berlose
0a454f0b5f Merge branch 'master' of github.com:tommasoberlose/another-widget into providers 2020-10-04 11:44:21 +02:00
Tommaso Berlose
b7bc93e174 Change the reset of the google steps 2020-10-04 11:44:05 +02:00
Tommaso Berlose
1dee4cc8e5 Add some providers 2020-10-04 11:43:36 +02:00
dylan
02b521c0b8 Fixed some translations 2020-10-03 21:42:15 -03:00
Tommaso Berlose
c04040e242 Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-10-03 17:36:41 +02:00
Tommaso Berlose
a208aa97b2
Merge pull request #196 from CoreyVidal/master
Replaced Product Sans with Google Sans
2020-10-03 17:36:28 +02:00
Tommaso Berlose
e3fda9457e Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-10-03 17:32:18 +02:00
Tommaso Berlose
b84631a2b2 Add privacy policy link 2020-10-03 17:32:03 +02:00
Corey Vidal
bad06c5762 Replaced Product Sans with Google Sans
Product Sans is designed to be used for Google product logos.
Google Sans is designed to be used for title, header, and body text.
2020-10-03 11:06:08 -04:00
Tommaso Berlose
74d8966f0a
Merge pull request #195 from kaprijela/master
Add Slovak translation
2020-10-03 01:27:25 +02:00
Tommaso Berlose
d8a76936a6 Remove tos 2020-10-03 01:24:40 +02:00
Tommaso Berlose
e72ca4fc6f Create the privacy policy 2020-10-03 01:23:40 +02:00
Gabriela Kandová
dd62212b06 Add Slovak translation 2020-10-02 20:26:45 +02:00
Tommaso Berlose
8dfc12e412 Update the README 2020-10-02 19:50:09 +02:00
Tommaso Berlose
2762f12151 Add the background location disclaimer 2020-10-02 19:46:52 +02:00
Tommaso Berlose
a873c71918 Update the ui 2020-10-02 18:02:22 +02:00
Tommaso Berlose
e8f3c110a8 Add the dark theme colors 2020-10-02 17:36:39 +02:00
Tommaso Berlose
72bf3b04a7 Fix #156 2020-10-02 16:16:32 +02:00
Tommaso Berlose
ca6fb75dfd Fix #191 2020-10-02 15:45:59 +02:00
Tommaso Berlose
59dc5de21a Fix #169 2020-10-02 15:00:37 +02:00
Tommaso Berlose
ec40a277d7 Fix #186, fix #187 2020-10-02 11:30:59 +02:00
Tommaso Berlose
507b7f2318 Fix #174, fix #185 2020-10-02 11:23:23 +02:00
Tommaso Berlose
1a709a9406
Merge pull request #172 from Matromag/patch-2
Update translation
2020-10-02 11:19:14 +02:00
Tommaso Berlose
770ba0cd13
Merge pull request #173 from Steve-Mr/master
Simplified Chinese translations
2020-10-02 11:18:51 +02:00
Tommaso Berlose
6a92225edf
Merge pull request #178 from shtraus12/master
fixed Russian localisation
2020-10-02 11:17:46 +02:00
Tommaso Berlose
ce801d4a7d
Merge pull request #192 from d-l-n/master
i have translated language app to Spanish
2020-10-02 11:16:59 +02:00
dylan
25da807bd5 Translated strings to Spanish (master branch) 2020-09-13 20:14:05 -03:00
dylan
63212d13b1 translated a few strings to Spanish (master branch) 2020-09-12 21:19:11 -03:00
dylan
79ad9b2500 updated file name lol 2020-09-11 22:26:25 -03:00
dylan
9c0aa0a9ef added model 2020-09-11 22:25:28 -03:00
Anton Chernykh
b9197ddf2e
fix Russian localisation 2020-06-30 15:17:50 +03:00
Steve Meng
d1362f7d81 modify chinese translation 2020-06-16 13:17:11 +08:00
Steve Meng
a8c7d115ae change one sentence 2020-06-13 16:16:40 +08:00
Steve Meng
a5a753e198 Add Chinese translation 2020-06-13 16:14:42 +08:00
Matromag
39ede20518
Update translation 2020-06-12 15:17:28 +02:00
Tommaso Berlose
5527e6e405
Merge pull request #161 from Drumber/translation
Update German translation
2020-05-25 10:59:41 +02:00
Tommaso Berlose
565ed11f53
Merge pull request #160 from Moutony/patch-11
Update French (v2.0.12)
2020-05-25 10:30:44 +02:00
Tommaso Berlose
4bd8653d32
Merge pull request #154 from zmni/translation
Update Indonesian translation
2020-05-25 10:30:32 +02:00
Tommaso Berlose
d121119ca9 Accept and close #162 2020-05-25 10:30:04 +02:00
Tommaso Berlose
e82a34bcc7 Fix #157, fix #163 2020-05-25 10:16:21 +02:00
Drumber
8867a1fbbd Update strings.xml
fixed just another too long string
2020-05-23 13:57:21 +02:00
Drumber
7265883d6c Update strings.xml
- fixed too long words
- small cosmetic changes
2020-05-23 13:39:28 +02:00
Drumber
7a4fc6ff58 Update German translation
- removed untranslatable strings
- added new string (line 203: nothing)
- fixed typos
2020-05-23 12:14:17 +02:00
Moutony
9f61215caa
Update French
I shortened the word 'événement' because it was oddly long.
It made the text overstep to a second line because of the new on/off button.
2020-05-23 12:01:39 +02:00
zmni
7ccf7eb8f6 Update Indonesian translation
Ref: 0d2287dbdf (diff-01dafb3fd0217441330103cfc8300094)
2020-05-23 04:32:15 +07:00
Tommaso Berlose
704448a848 Double-tap on the capitalize icon to set the date to uppercase 2020-05-22 19:25:34 +02:00
Tommaso Berlose
fa366b3d45
Merge pull request #151 from shtraus12/master
Fixed Russian translation
2020-05-22 19:22:16 +02:00
Tommaso Berlose
e58fef66aa Close #148 2020-05-22 19:18:29 +02:00
Tommaso Berlose
94825808f4 Update the UI 2020-05-22 18:51:42 +02:00
Anton Chernykh
c120bad9c6
Update strings.xml 2020-05-22 19:48:39 +03:00
Tommaso Berlose
0d2287dbdf Fix #144 2020-05-22 18:30:38 +02:00
Tommaso Berlose
9e40586456 Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-05-22 18:20:29 +02:00
Tommaso Berlose
4d75f4ca0c Fix #150 2020-05-22 18:20:08 +02:00
Tommaso Berlose
0859632803
Merge pull request #147 from Drumber/master
Added German translation (values-de)
2020-05-22 17:14:24 +02:00
Tommaso Berlose
47562b35ca
Merge pull request #146 from zmni/translation
Update Indonesian translation
2020-05-22 17:14:19 +02:00
Tommaso Berlose
0f4f02ea28
Merge pull request #138 from Moutony/patch-10
Update strings.xml (values-fr) - May 20 version
2020-05-22 17:14:12 +02:00
Tommaso Berlose
31cf950eee Update ui 2020-05-22 17:13:06 +02:00
Lars
f230d300ee
fixed minor spelling mistakes 2020-05-21 18:10:55 +02:00
Drumber
c610857056 Added German translation (values-de) 2020-05-21 17:26:01 +02:00
Tommaso Berlose
770040ad93 Fix #136, fix #142 2020-05-21 13:30:15 +02:00
zmni
f784817296 Update Indonesian translation 2020-05-21 05:03:57 +07:00
Tommaso Berlose
ec1c25cb4c Bugfixes 2020-05-20 20:53:22 +02:00
Tommaso Berlose
e1d2f5a782 Update the UI 2020-05-20 20:46:21 +02:00
Moutony
6e8c6cf055
Update strings.xml 2020-05-20 20:04:11 +02:00
Moutony
68b5997e8d
Update strings.xml 2020-05-20 19:47:53 +02:00
Moutony
56b21be946
Update stings.xml (values-fr) - Glance
I translated the Glance explanations (line 145).
I shortened the "title-show-glance" (line 133) because it was oddly long (see screenshot)
2020-05-20 18:37:58 +02:00
Moutony
fdc02b2cef
Update strings.xml
I translated the Glance explanations.
2020-05-20 18:06:40 +02:00
Tommaso Berlose
863b8f79d8 Bump the version 2020-05-20 13:46:27 +02:00
Tommaso Berlose
857a8009b6 Fix #137 2020-05-20 13:37:37 +02:00
Tommaso Berlose
9199f28ad9 Fix french translation and add a new icon pack 2020-05-20 13:34:32 +02:00
Tommaso Berlose
ddb1b7494a
Merge pull request #135 from Moutony/patch-7
Update stings.xml (values-fr) - May 19, 2020 ver.
2020-05-20 12:19:51 +02:00
Moutony
fd2b1ba976
Update strings.xml
Some translations may be oddly long. I will correct them once I see what they look like in the app.
2020-05-19 20:12:02 +02:00
Tommaso Berlose
90f0d7de00 Build v2.0.10 2020-05-19 20:07:08 +02:00
Tommaso Berlose
66ad5e0839 Fix #133 2020-05-19 20:01:11 +02:00
Tommaso Berlose
2ac7e072e5 Close #134 2020-05-19 19:49:21 +02:00
Tommaso Berlose
a3392dabcf Add a new cool icon pack 2020-05-19 19:48:33 +02:00
Tommaso Berlose
c2c54a04d2
Merge pull request #131 from LastInLine/patch-1
Update strings.xml
2020-05-19 10:28:33 +02:00
Tommaso Berlose
2c237058b3 Close #132 2020-05-19 10:27:54 +02:00
LastInLine
aa2b4b0510
Update strings.xml
Singular/plural switch for weather info visibility and changed awkward phrasing around time remaining until an event.
2020-05-18 21:38:56 -04:00
Tommaso Berlose
4c27d1dd9b Fix future events 2020-05-18 20:36:59 +02:00
Tommaso Berlose
eba5575ee2 Fix event not found error 2020-05-18 14:14:32 +02:00
Tommaso Berlose
aff7e407ca
Merge pull request #127 from chreddy/patch-2
Adding Danish translation
2020-05-18 12:47:39 +02:00
Tommaso Berlose
f7fc31d968 Fix all day events widget date 2020-05-18 12:44:46 +02:00
chreddy
90b588603d
Update strings.xml
settings_general_title was a mistake indeed. This has been changed.

As for settings_second_row_info_subtitle_0, this has been translated correctly already. However I noticed that I must've overlooked next_alarm_warning previously, so I've added a translation of this as well.
2020-05-18 12:38:57 +02:00
Tommaso Berlose
5d9bd11abb Fix #129 2020-05-18 12:19:15 +02:00
Tommaso Berlose
01005ec443 Clean the project 2020-05-18 12:07:04 +02:00
Tommaso Berlose
eaf6400e8b
Merge pull request #84 from zmni/translation
Create Indonesian translation
2020-05-18 12:05:21 +02:00
Tommaso Berlose
30339c7375
Merge pull request #124 from shtraus12/master
Added Russian translation (values-ru)
2020-05-18 12:00:08 +02:00
Tommaso Berlose
87da284be4 Update strings 2020-05-18 11:49:57 +02:00
chreddy
573c6d03e5
Adding Danish translation
I re-did my previous translation, based on the newly updated string.xml.
Because of this, I've closed the previous pull request as well.
2020-05-18 00:27:53 +02:00
Anton Chernykh
79e87b0648
Add files via upload 2020-05-17 16:51:41 +03:00
zmni
9e9a91690e Update Indonesian translation 2020-05-17 19:12:38 +07:00
Tommaso Berlose
5f699af509 Show translated in-app purchases 2020-05-17 13:25:19 +02:00
zmni
aae40b9dd3 Merge branch 'master' into translation
* master:
  Update strings
  Add settings header
  Add battery level handler
  Fix #120
2020-05-17 18:22:52 +07:00
Tommaso Berlose
cebd679856 Update strings 2020-05-17 13:19:55 +02:00
Tommaso Berlose
06443ddddb Add settings header 2020-05-16 20:24:11 +02:00
Tommaso Berlose
233761a169 Add battery level handler 2020-05-16 14:34:04 +02:00
Tommaso Berlose
b81461f725 Fix #120 2020-05-16 12:47:29 +02:00
zmni
26b1948b70 Update Indonesian translation 2020-05-14 22:49:58 +07:00
zmni
af64818dff Merge branch 'master' into translation
* master:
  Update the build version
  Update event repository
  Update the events selector. Fix #95
  Fix #113, #111, #79, #107, #109
  Add weather icon pack, updates frequency and fix google fit
  Update ui
  Update icons
  Fix dark theme toggle issue
  Bugfixes
  Update google fit integration
  Update beta release
  update gitignore
  Clean project files
  Add google fit connection
  Add google fit integration
2020-05-14 22:14:50 +07:00
Tommaso Berlose
b04b103634 Update the build version 2020-05-13 19:04:17 +02:00
Tommaso Berlose
1dc050e77f Update event repository 2020-05-13 18:58:45 +02:00
Tommaso Berlose
ff171d4022 Update the events selector. Fix #95 2020-05-13 18:57:44 +02:00
Tommaso Berlose
d91471d1ee Fix #113, #111, #79, #107, #109 2020-05-13 17:42:48 +02:00
Tommaso Berlose
ac381c8542 Add weather icon pack, updates frequency and fix google fit 2020-05-12 23:54:07 +02:00
Tommaso Berlose
ba5a860e75 Update ui 2020-05-11 13:33:37 +02:00
Tommaso Berlose
09bc9df22f Update icons 2020-05-11 13:30:36 +02:00
Tommaso Berlose
385806413e Fix dark theme toggle issue 2020-05-11 12:40:42 +02:00
Tommaso Berlose
7d2ea5a4d8 Bugfixes 2020-05-11 00:38:32 +02:00
Tommaso Berlose
ce9f5a6e45 Update google fit integration 2020-05-11 00:00:31 +02:00
zmni
f28596c194 Merge branch 'master' into translation
* master:
  Gix switcher, update glance, add glance order, add ampm toggle
  Merge crud-device
  Add dividers toggle
  Add integreations activity, removed long time until intervals
  Change music fragment to at a glance
  Update build version
  Fix #83
  Add current song
  Add clock text color
2020-05-10 04:27:13 +07:00
zmni
7f41a92a91 Update Indonesian translation 2020-05-10 04:06:26 +07:00
zmni
40c9253159 Create Indonesian translation 2020-05-08 19:04:48 +07:00
2843 changed files with 21908 additions and 8541 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['paypal.me/tommasoberlose']

8
.gitignore vendored
View File

@ -1,12 +1,14 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea/workspace.xml /.idea/*
/.idea/libraries
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
/tasksintegration/build /tasksintegration/build
apikey.properties
/app/google-services.json /app/google-services.json
apikey.properties
./.idea/*
app/release/*
/app/release/*

View File

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

Binary file not shown.

View File

@ -1,6 +1,22 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">

4
.idea/gradle.xml generated
View File

@ -4,16 +4,16 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

2
.idea/misc.xml generated
View File

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

7
.idea/modules.xml generated
View File

@ -2,8 +2,11 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/Another Widget.iml" filepath="$PROJECT_DIR$/Another Widget.iml" group="Another Widget" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/Another_Widget.iml" filepath="$PROJECT_DIR$/.idea/modules/Another_Widget.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="Another Widget/app" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" />
</modules> </modules>
</component> </component>
</project> </project>

140
.idea/navEditor.xml generated
View File

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

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -11,7 +11,14 @@ While respecting the design of the application, there is a great opportunity to
Also, as much as possible, there are always updates and new features in the short run. Also, as much as possible, there are always updates and new features in the short run.
Help me developing with feedback and support me on how you can. Help me developing with feedback and support me on how you can.
<div style="text-align:center"><a href="https://play.google.com/store/apps/details?id=com.tommasoberlose.anotherwidget" target="_blank"><img src="google-play-badge.png" height="100" /></a></div> <div style="text-align:center"><a href='https://play.google.com/store/apps/details?id=com.tommasoberlose.anotherwidget&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height='100px' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png'/></a></div>
Help with translations
-------
Hey! You could view the file strings.xml ([here](https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml)) that contains the English version of the app strings.
You have to copy the file, create a copy of it inside the folder values-[LANGUAGE-SUFFIX] with the translated strings and create a pull request to submit your changes.
License License
------- -------

View File

@ -1,33 +1,22 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
// Apply the Crashlytics Gradle plugin
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android'
def apiKeyPropertiesFile = rootProject.file("apikey.properties")
def apiKeyProperties = new Properties()
apiKeyProperties.load(new FileInputStream(apiKeyPropertiesFile))
android { android {
compileSdkVersion 29
buildToolsVersion "29.0.3" compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId "com.tommasoberlose.anotherwidget" applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 30
versionCode 85 versionCode 139
versionName "2.0.8" versionName "2.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [ "AWARENESS_API_KEY": apiKeyProperties['AWARENESS_API_KEY']] renderscriptSupportModeEnabled true
} }
buildTypes { buildTypes {
@ -53,83 +42,85 @@ android {
packagingOptions { packagingOptions {
exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/DEPENDENCIES'
} }
dataBinding {
enabled = true
}
viewBinding.enabled = true buildFeatures {
dataBinding = true
viewBinding = true
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI // UI
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.2.0-alpha06' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.3.0'
implementation 'net.idik:slimadapter:2.1.2' implementation 'net.idik:slimadapter:2.1.2'
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
implementation 'com.kyleduo.switchbutton:library:2.0.3'
// Lifecycle // Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.work:work-runtime-ktx:2.3.4" implementation "androidx.work:work-runtime-ktx:2.5.0"
// EventBus // EventBus
implementation 'org.greenrobot:eventbus:3.1.1' implementation 'org.greenrobot:eventbus:3.2.0'
// Room
implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
// Navigation // Navigation
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha05' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// Other // Other
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'joda-time:joda-time:2.9.9' implementation 'net.danlew:android.joda:2.10.9'
implementation 'me.everything:providers-android:1.0.1' implementation 'me.everything:providers-android:1.0.1'
implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2' implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
//Glide //Glide
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0'
// Fitness
implementation 'com.google.android.gms:play-services-fitness:18.0.0'
implementation 'com.google.android.gms:play-services-auth:18.0.0'
//Weather //Weather
implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2' implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2'
implementation 'com.google.android.gms:play-services-location:17.0.0'
// Billing
implementation 'com.android.billingclient:billing:2.2.0'
implementation 'com.android.billingclient:billing-ktx:2.2.0'
// KTX // KTX
implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.palette:palette-ktx:1.0.0" implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.core:core-ktx:1.5.0'
// Add the Firebase SDK for Crashlytics. //Retrofit
implementation 'com.google.firebase:firebase-crashlytics:17.0.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1"
//Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Preferences // Preferences
implementation 'com.chibatching.kotpref:kotpref:2.10.0' implementation 'com.chibatching.kotpref:kotpref:2.13.1'
implementation 'com.chibatching.kotpref:livedata-support:2.10.0' implementation 'com.chibatching.kotpref:livedata-support:2.13.1'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
// Permissions // Permissions
implementation 'com.karumi:dexter:6.1.0' implementation 'com.karumi:dexter:6.2.1'
// Billing // Fonts
implementation 'com.android.billingclient:billing:2.2.0' implementation 'com.github.firatkarababa:downloadable-font-list-library:1.0.2'
implementation 'com.android.billingclient:billing-ktx:2.2.0'
} }

Binary file not shown.

View File

@ -5,46 +5,49 @@
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/> <uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/my_backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:name=".AWApplication" android:name=".AWApplication"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity"> tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.MainActivity" android:launchMode="singleInstance" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait"> <activity android:name=".ui.activities.SplashActivity" android:exported="true" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.activities.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomDateActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget" android:exported="true">
<receiver android:name=".ui.widgets.MainWidget">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/the_widget_info" /> android:resource="@xml/the_widget_info" />
@ -70,8 +73,8 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_CALENDAR_UPDATE" /> <action android:name="com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_TIME_UPDATE" /> <action android:name="com.tommasoberlose.anotherwidget.action.TIME_UPDATE" />
<action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" /> <action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" /> <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
@ -86,65 +89,25 @@
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_WEATHER_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" /> <action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" /> <action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.LOCALE_CHANGED" /> <action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".receivers.PlayerReceiver"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.android.music.metachanged" />
<action android:name="com.android.music.playstatechanged" />
<action android:name="com.android.music.playbackcomplete" />
<action android:name="com.android.music.queuechanged" />
<action android:name="com.htc.music.metachanged" />
<action android:name="fm.last.android.metachanged" />
<action android:name="com.sec.android.app.music.metachanged" />
<action android:name="com.nullsoft.winamp.metachanged" />
<action android:name="com.amazon.mp3.metachanged" />
<action android:name="com.miui.player.metachanged" />
<action android:name="com.real.IMP.metachanged" />
<action android:name="com.sonyericsson.music.metachanged" />
<action android:name="com.rdio.android.metachanged" />
<action android:name="com.samsung.sec.android.MusicPlayer.metachanged" />
<action android:name="com.andrew.apollo.metachanged" />
<action android:name="com.spotify.music.playbackstatechanged"/>
<action android:name="com.spotify.music.metadatachanged"/>
<action android:name="com.spotify.music.queuechanged"/>
</intent-filter>
</receiver>
<receiver <receiver
android:name=".receivers.WidgetClickListenerReceiver" android:name=".receivers.WidgetClickListenerReceiver"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_OPEN_WEATHER_INTENT" /> <action android:name="com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <service android:name=".receivers.NotificationListener" android:exported="true"
android:name=".receivers.CrashlyticsReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_REPORT_CRASH" />
</intent-filter>
</receiver>
<service android:name=".services.EventListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".receivers.MusicNotificationListener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter> <intent-filter>
<action android:name="android.service.notification.NotificationListenerService" /> <action android:name="android.service.notification.NotificationListenerService" />
@ -159,20 +122,15 @@
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
<action android:name="android.intent.action.BATTERY_LOW"/> <action android:name="android.intent.action.BATTERY_LOW"/>
<action android:name="android.intent.action.BATTERY_OKAY"/> <action android:name="android.intent.action.BATTERY_OKAY"/>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receivers.FenceReceiver"
android:exported="false"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
<intent-filter>
<action android:name="com.mypackage.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.awareness.API_KEY"
android:value="${AWARENESS_API_KEY}"/>
</application> </application>
<queries>
<package android:name="com.google.android.apps.fitness"/>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,49 +1,18 @@
package com.tommasoberlose.anotherwidget package com.tommasoberlose.anotherwidget
import android.app.Application import android.app.Application
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.chibatching.kotpref.Kotpref import com.chibatching.kotpref.Kotpref
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import io.realm.Realm
import io.realm.RealmConfiguration
class AWApplication : Application() { class AWApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// Firebase crashlitycs
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
// Preferences // Preferences
Kotpref.init(this) Kotpref.init(this)
// Dark theme // Dark theme
AppCompatDelegate.setDefaultNightMode(Preferences.darkThemePreference) AppCompatDelegate.setDefaultNightMode(Preferences.darkThemePreference)
// Realm
Realm.init(this)
val config = RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)
calibrateVersions()
} }
}
private fun calibrateVersions() {
// 2.0 Tolerance
if (Preferences.clockTextSize > 50f) {
Preferences.clockTextSize = 32f
}
if (Preferences.textMainSize > 36f) {
Preferences.textMainSize = 32f
}
if (Preferences.textSecondSize > 28f) {
Preferences.textSecondSize = 24f
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,69 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
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
class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var binding: WeatherProviderSettingsLayoutBinding = WeatherProviderSettingsLayoutBinding.inflate(android.view.LayoutInflater.from(context))
init {
binding.apiKeyContainer.isVisible = WeatherHelper.isKeyRequired()
binding.actionSaveKey.isVisible = WeatherHelper.isKeyRequired()
WeatherHelper.getProviderInfoTitle(context).let { title ->
binding.infoTitle.text = title
binding.infoTitle.isVisible = title != ""
}
WeatherHelper.getProviderInfoSubtitle(context).let { subtitle ->
binding.infoSubtitle.text = subtitle
binding.infoSubtitle.isVisible = subtitle != ""
}
binding.infoProvider.text = WeatherHelper.getProviderName(context)
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
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather
Constants.WeatherProvider.WEATHER_GOV,
Constants.WeatherProvider.YR,
null -> ""
})
binding.actionOpenProvider.setOnClickListener {
context.openURI(WeatherHelper.getProviderLink())
}
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
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi = key
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere = key
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather = key
else -> {}
}
callback.invoke()
dismiss()
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -1,27 +1,34 @@
package com.tommasoberlose.anotherwidget.components package com.tommasoberlose.anotherwidget.components
import android.content.Context import android.content.Context
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.CustomNotesDialogLayoutBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import kotlinx.android.synthetic.main.custom_notes_dialog_layout.view.*
class CustomNotesDialog(context: Context) : 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 { init {
val view = View.inflate(context, R.layout.custom_notes_dialog_layout, null) binding.notes.setText(Preferences.customNotes)
view.notes.setText(Preferences.customNotes)
view.action_positive.setOnClickListener { binding.actionPositive.setOnClickListener {
Preferences.customNotes = view.notes.text.toString() Preferences.customNotes = binding.notes.text.toString()
this.dismiss() this.dismiss()
callback?.invoke()
} }
view.notes.requestFocus() binding.notes.requestFocus()
setContentView(view) setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
} }
} }

View File

@ -0,0 +1,25 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.ScrollView
import androidx.core.widget.NestedScrollView
class FixedFocusScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : NestedScrollView(context, attrs, defStyle) {
var isScrollable = true
override fun scrollTo(x: Int, y: Int) {
if (isScrollable || !isLaidOut) {
super.scrollTo(x, y)
}
}
}

View File

@ -1,99 +0,0 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
import com.tommasoberlose.anotherwidget.models.GlanceProvider
import kotlinx.android.synthetic.main.glance_provider_sort_bottom_menu.view.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.util.*
import kotlin.collections.ArrayList
class GlanceProviderSortMenu(
context: Context
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private lateinit var adapter: SlimAdapter
override fun show() {
val view = View.inflate(context, R.layout.glance_provider_sort_bottom_menu, null)
// Header
view.header_text.text = context.getString(R.string.settings_sort_glance_providers_title)
// List
adapter = SlimAdapter.create()
view.menu.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(context)
view.menu.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<GlanceProvider>(R.layout.glance_provider_item) { item, injector ->
injector
.text(R.id.title, item.title)
.with<ImageView>(R.id.icon) {
it.setImageDrawable(ContextCompat.getDrawable(context, item.icon))
}
}
.attachTo(view.menu)
val mIth = ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT
) {
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)
val list = GlanceProviderHelper.getGlanceProviders()
Collections.swap(list, fromPos, toPos)
GlanceProviderHelper.saveGlanceProviderOrder(list)
return true
}
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
// remove from adapter
}
})
mIth.attachToRecyclerView(view.menu)
adapter.updateData(
GlanceProviderHelper.getGlanceProviders()
.filter { it != Constants.GlanceProviderId.BATTERY_LEVEL_LOW }
.mapNotNull { GlanceProviderHelper.getGlanceProviderById(context, it) }
)
setContentView(view)
super.show()
}
}

View File

@ -0,0 +1,292 @@
package com.tommasoberlose.anotherwidget.components
import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.view.LayoutInflater
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog
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.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.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() {
/* TITLE */
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)
Constants.GlanceProviderId.CUSTOM_INFO -> context.getString(R.string.settings_custom_notes_title)
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)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_title)
}
/* SUBTITLE*/
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)
Constants.GlanceProviderId.CUSTOM_INFO -> ""
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)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_subtitle)
}
/* SONG */
binding.actionFilterMusicPlayers.isVisible = provider == Constants.GlanceProviderId.PLAYING_SONG
binding.actionChangeMediaInfoFormat.isVisible = provider == Constants.GlanceProviderId.PLAYING_SONG
if (provider == Constants.GlanceProviderId.PLAYING_SONG) {
binding.actionFilterMusicPlayers.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, MusicPlayersFilterActivity::class.java), 0)
}
binding.actionChangeMediaInfoFormat.setOnClickListener {
dismiss()
context.startActivityForResult(Intent(context, MediaInfoFormatActivity::class.java), 0)
}
checkNotificationPermission()
}
/* ALARM */
binding.alarmSetByContainer.isVisible = provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM
if (provider == Constants.GlanceProviderId.NEXT_CLOCK_ALARM) {
binding.header.text = context.getString(R.string.information_header)
binding.warningContainer.isVisible = false
checkNextAlarm()
}
/* BATTERY INFO */
if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) {
binding.warningContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
}
/* NOTIFICATIONS */
binding.actionFilterNotificationsApp.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
binding.actionChangeNotificationTimer.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
if (provider == Constants.GlanceProviderId.NOTIFICATIONS) {
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) {
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()
}
/* WEATHER */
if (provider == Constants.GlanceProviderId.WEATHER) {
binding.header.isVisible = false
binding.divider.isVisible = false
checkWeatherConfig()
}
/* TOGGLE */
binding.providerSwitch.setCheckedImmediatelyNoEvent(when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> Preferences.showNextAlarm
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> Preferences.showBatteryCharging
Constants.GlanceProviderId.CUSTOM_INFO -> true
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> Preferences.showDailySteps
Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications
Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings
Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider
Constants.GlanceProviderId.WEATHER -> Preferences.showWeatherAsGlanceProvider
})
var job: Job? = null
binding.providerSwitch.setOnCheckedChangeListener { _, isChecked ->
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
delay(300)
withContext(Dispatchers.Main) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
Preferences.showMusic = isChecked
checkNotificationPermission()
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked
checkNextAlarm()
if (!isChecked)
AlarmHelper.clearTimeout(context)
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked
checkLastNotificationsPermission()
if (!isChecked)
ActiveNotificationsHelper.clearLastNotification(context)
}
Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked
GreetingsHelper.toggleGreetings(context)
}
Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked
if (isChecked) {
com.tommasoberlose.anotherwidget.db.EventRepository(context).run {
resetNextEventData()
close()
}
}
}
Constants.GlanceProviderId.WEATHER -> {
Preferences.showWeatherAsGlanceProvider = isChecked
}
else -> {
}
}
statusCallback?.invoke()
}
}
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
private fun checkNextAlarm() {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) {
val pm = context.packageManager as PackageManager
val appNameOrPackage = try {
pm.getApplicationLabel(pm.getApplicationInfo(alarm.showIntent?.creatorPackage ?: "", 0))
} catch (e: Exception) {
alarm.showIntent?.creatorPackage ?: ""
}
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 {
binding.alarmSetByContainer.isVisible = false
binding.header.isVisible = false
binding.divider.isVisible = false
}
}
statusCallback?.invoke()
}
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 checkWeatherConfig() {
if (!Preferences.showWeather || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || Preferences.weatherProviderLocationError != "") {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_show_weather_as_glance_provider_error)
binding.warningContainer.setOnClickListener {
dismiss()
EventBus.getDefault().post(MainFragment.ChangeTabEvent(1))
}
} else {
binding.warningContainer.isVisible = false
}
}
private fun checkNotificationPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
binding.warningContainer.isVisible = false
MediaPlayerHelper.updatePlayingMediaInfo(context)
}
Preferences.showMusic -> {
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 -> {
binding.warningContainer.isVisible = false
}
}
statusCallback?.invoke()
}
private fun checkLastNotificationsPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
binding.warningContainer.isVisible = false
}
Preferences.showNotifications -> {
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 -> {
binding.warningContainer.isVisible = false
}
}
statusCallback?.invoke()
}
}

View File

@ -0,0 +1,59 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
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
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() {
// Header
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
binding.warningText.isVisible = false
// Menu
for (item in Constants.WeatherIconPack.values()) {
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
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>(itemBinding.icon1, itemBinding.icon2, itemBinding.icon3, itemBinding.icon4).forEach {
if (item == Constants.WeatherIconPack.MINIMAL) {
it.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimaryText))
} else {
it.setColorFilter(ContextCompat.getColor(context, android.R.color.transparent))
}
}
itemBinding.root.setOnClickListener {
Preferences.weatherIconPack = item.rawValue
this.dismiss()
}
binding.menu.addView(itemBinding.root)
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
}

View File

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

View File

@ -0,0 +1,21 @@
package com.tommasoberlose.anotherwidget.components
import android.view.View
import android.widget.CompoundButton
class MenuItem (
val icon: Int,
val getIcon: (() -> Int)? = null,
val title: String,
val label: String = "",
val getLabel: (() -> String)? = null,
val isEnabled: (() -> Boolean) = fun (): Boolean { return true },
val onClick: View.OnClickListener? = null,
val onLongClick: View.OnLongClickListener? = null,
val showToggle: Boolean = false,
val toggleValue: (() -> Boolean) = fun (): Boolean { return false },
val onToggle: CompoundButton.OnCheckedChangeListener? = null,
val showPermission: (() -> Boolean) = fun (): Boolean { return false },
val onPermissionClickListener: View.OnClickListener? = null,
val render: ((view: View) -> Unit)? = null
)

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

@ -1,56 +1,75 @@
package com.tommasoberlose.anotherwidget.db package com.tommasoberlose.anotherwidget.db
import android.content.Context import android.content.Context
import android.provider.CalendarContract
import android.util.Log import android.util.Log
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase
import com.chibatching.kotpref.bulk import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import io.realm.Realm
import io.realm.RealmResults
import java.util.* import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class EventRepository(val context: Context) { class EventRepository(val context: Context) {
private val realm by lazy { Realm.getDefaultInstance() } private val db by lazy { EventDatabase.getDatabase(context) }
fun saveEvents(eventList: ArrayList<Event>) { fun saveEvents(eventList: List<Event>) {
realm.executeTransactionAsync { realm -> db.runInTransaction{
realm.where(Event::class.java).findAll().deleteAllFromRealm() db.dao().run {
realm.copyToRealm(eventList) deleteAll()
insert(eventList)
}
} }
} }
fun resetNextEventData() { fun clearEvents() {
realm.executeTransactionAsync { db.dao().deleteAll()
it.where(Event::class.java).findAll().deleteAllFromRealm() }
}
fun resetNextEventData() {
Preferences.bulk { Preferences.bulk {
remove(Preferences::nextEventId) remove(Preferences::nextEventId)
remove(Preferences::nextEventName)
remove(Preferences::nextEventStartDate)
remove(Preferences::nextEventAllDay)
remove(Preferences::nextEventLocation)
remove(Preferences::nextEventEndDate)
remove(Preferences::nextEventCalendarId)
} }
} }
fun saveNextEventData(event: Event) { fun saveNextEventData(event: Event) {
Preferences.nextEventId = event.eventID Preferences.nextEventId = event.id
} }
fun getNextEvent(): Event? { fun getNextEvent(): Event? {
val nextEvent = getEventByEventId(Preferences.nextEventId) val nextEvent = getEventById(Preferences.nextEventId)
return if (nextEvent != null && nextEvent.endDate > Calendar.getInstance().timeInMillis) { val now = Calendar.getInstance().timeInMillis
val limit = Calendar.getInstance().apply {
timeInMillis = now
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)
}
}
return if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate <= limit.timeInMillis) {
nextEvent nextEvent
} else { } else {
val events = getEvents() val events = getEvents()
if (events.isNotEmpty()) { if (events.isNotEmpty()) {
val newNextEvent = events.first() val newNextEvent = events.first()
Preferences.nextEventId = newNextEvent!!.eventID saveNextEventData(newNextEvent)
newNextEvent newNextEvent
} else { } else {
resetNextEventData() resetNextEventData()
@ -59,44 +78,114 @@ class EventRepository(val context: Context) {
} }
} }
fun getEventByEventId(id: Long): Event? = realm.where(Event::class.java).equalTo("eventID", id).findFirst() fun getEventById(id: Long): Event? {
return db.dao().findById(id)
}
fun goToNextEvent() { fun goToNextEvent() {
val eventList = getEvents() val eventList = getEvents()
if (eventList.isNotEmpty()) { if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId } val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
if (index > -1 && index < eventList.size - 1) { if (index > -1 && index < eventList.size - 1) {
Preferences.nextEventId = eventList[index + 1]!!.eventID saveNextEventData(eventList[index + 1])
} else { } else {
Preferences.nextEventId = eventList.first()!!.eventID saveNextEventData(eventList.first())
} }
} else { } else {
resetNextEventData() resetNextEventData()
} }
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
} }
fun goToPreviousEvent() { fun goToPreviousEvent() {
val eventList = getEvents() val eventList = getEvents()
if (eventList.isNotEmpty()) { if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId } val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
if (index > 0) { if (index > 0) {
Preferences.nextEventId = eventList[index - 1]!!.eventID saveNextEventData(eventList[index - 1])
} else { } else {
Preferences.nextEventId = eventList.last()!!.eventID saveNextEventData(eventList.last())
} }
} else { } else {
resetNextEventData() resetNextEventData()
} }
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
} }
fun getEvents(): RealmResults<Event> { fun getFutureEvents(): List<Event> {
return db.dao().find(Calendar.getInstance().timeInMillis).sortEvents()
}
private fun getEvents(): List<Event> {
val now = Calendar.getInstance().timeInMillis val now = Calendar.getInstance().timeInMillis
return realm.where(Event::class.java).greaterThan("endDate", now).findAll() val limit = Calendar.getInstance().apply {
timeInMillis = now
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)
}
}
return db.dao().find(now, limit.timeInMillis).sortEvents()
} }
fun getEventsCount(): Int = getEvents().size fun getEventsCount(): Int = getEvents().size
}
fun close() {
// db.close()
}
@Dao
interface EventDao {
@Query("SELECT * FROM events WHERE id = :id LIMIT 1")
fun findById(id: Long): Event?
@Query("SELECT * FROM events WHERE end_date > :from")
fun find(from: Long): List<Event>
@Query("SELECT * FROM events WHERE end_date > :from AND start_date <= :to")
fun find(from: Long, to: Long): List<Event>
@Insert
fun insert(events: List<Event>)
@Query("DELETE FROM events")
fun deleteAll()
}
@Database(entities = arrayOf(Event::class), version = 1, exportSchema = false)
abstract class EventDatabase : RoomDatabase() {
abstract fun dao(): EventDao
companion object {
private var INSTANCE: EventDatabase? = null
fun getDatabase(context: Context): EventDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
EventDatabase::class.java,
"events"
).allowMainThreadQueries().build()
INSTANCE = instance
// return instance
instance
}
}
}
}
}

View File

@ -2,13 +2,16 @@ package com.tommasoberlose.anotherwidget.global
object Actions { object Actions {
const val ACTION_EXTRA_OPEN_WEATHER_PROVIDER = "ACTION_EXTRA_OPEN_WEATHER_PROVIDER" const val ACTION_EXTRA_OPEN_WEATHER_PROVIDER = "ACTION_EXTRA_OPEN_WEATHER_PROVIDER"
const val ACTION_EXTRA_DISABLE_GPS_NOTIFICATION = "ACTION_EXTRA_DISABLE_GPS_NOTIFICATION"
const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.TIME_UPDATE" const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.TIME_UPDATE"
const val ACTION_ALARM_UPDATE = "com.tommasoberlose.anotherwidget.action.ALARM_UPDATE"
const val ACTION_CALENDAR_UPDATE = "com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE" const val ACTION_CALENDAR_UPDATE = "com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE"
const val ACTION_WEATHER_UPDATE = "com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE" const val ACTION_WEATHER_UPDATE = "com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE"
const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT"
const val ACTION_GO_TO_NEXT_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_NEXT_EVENT" const val ACTION_GO_TO_NEXT_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_NEXT_EVENT"
const val ACTION_GO_TO_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT" 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,25 +1,97 @@
package com.tommasoberlose.anotherwidget.global package com.tommasoberlose.anotherwidget.global
import java.text.SimpleDateFormat
object Constants { object Constants {
const val RESULT_CODE_CUSTOM_LOCATION = 45 const val RESULT_CODE_CUSTOM_LOCATION = 45
const val RESULT_APP_NAME = "RESULT_APP_NAME" const val RESULT_APP_NAME = "RESULT_APP_NAME"
const val RESULT_APP_PACKAGE = "RESULT_APP_PACKAGE" const val RESULT_APP_PACKAGE = "RESULT_APP_PACKAGE"
const val CUSTOM_FONT_PRODUCT_SANS = 1 const val CUSTOM_FONT_GOOGLE_SANS = 1
enum class ClockBottomMargin(val value: Int) { const val CUSTOM_FONT_DOWNLOADED = 2
const val CUSTOM_FONT_DOWNLOAD_NEW = 3
enum class ClockBottomMargin(val rawValue: Int) {
NONE(0), NONE(0),
SMALL(1), SMALL(1),
MEDIUM(2), MEDIUM(2),
LARGE(3) LARGE(3)
} }
enum class SecondRowTopMargin(val rawValue: Int) {
NONE(0),
SMALL(1),
MEDIUM(2),
LARGE(3)
}
enum class Dimension(val rawValue: Float) {
NONE(0f),
SMALL(8f),
MEDIUM(16f),
LARGE(24f)
}
enum class GlanceProviderId(val id: String) { enum class GlanceProviderId(val id: String) {
PLAYING_SONG("PLAYING_SONG"), PLAYING_SONG("PLAYING_SONG"),
NEXT_CLOCK_ALARM("NEXT_CLOCK_ALARM"), NEXT_CLOCK_ALARM("NEXT_CLOCK_ALARM"),
BATTERY_LEVEL_LOW("BATTERY_LEVEL_LOW"), BATTERY_LEVEL_LOW("BATTERY_LEVEL_LOW"),
CUSTOM_INFO("CUSTOM_INFO"), CUSTOM_INFO("CUSTOM_INFO"),
GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS") GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS"),
NOTIFICATIONS("NOTIFICATIONS"),
GREETINGS("GREETINGS"),
EVENTS("EVENTS"),
WEATHER("WEATHER");
companion object {
private val map = GlanceProviderId.values().associateBy(GlanceProviderId::id)
fun from(type: String) = map[type]
}
}
enum class WidgetUpdateFrequency(val rawValue: Int) {
LOW(0),
DEFAULT(1),
HIGH(2)
}
enum class WeatherProvider(val rawValue: Int) {
OPEN_WEATHER(0),
WEATHER_BIT(1),
WEATHER_API(2),
HERE(3),
ACCUWEATHER(4),
WEATHER_GOV(5),
YR(6);
companion object {
private val map = WeatherProvider.values().associateBy(WeatherProvider::rawValue)
fun fromInt(type: Int) = map[type]
}
}
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 package com.tommasoberlose.anotherwidget.global
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate.* import androidx.appcompat.app.AppCompatDelegate.*
import androidx.core.os.ConfigurationCompat
import com.chibatching.kotpref.KotprefModel import com.chibatching.kotpref.KotprefModel
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.utils.isMetric
object Preferences : KotprefModel() { object Preferences : KotprefModel() {
override val commitAllPropertiesByDefault: Boolean = true override val commitAllPropertiesByDefault: Boolean = true
@ -14,83 +17,137 @@ object Preferences : KotprefModel() {
var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false) var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false)
var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "") var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "")
var weatherTemp by floatPref(key = "PREF_WEATHER_TEMP", default = 0f) var weatherTemp by floatPref(key = "PREF_WEATHER_TEMP", default = 0f)
var weatherTempUnit by stringPref(key = "PREF_WEATHER_TEMP_UNIT", default = "F") var weatherTempUnit by stringPref(key = "PREF_WEATHER_TEMP_UNIT", default = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) "C" else "F")
var weatherRealTempUnit by stringPref(key = "PREF_WEATHER_REAL_TEMP_UNIT", default = "F") var weatherRealTempUnit by stringPref(key = "PREF_WEATHER_REAL_TEMP_UNIT", default = if (ConfigurationCompat.getLocales(context.resources.configuration)[0].isMetric()) "C" else "F")
var calendarAllDay by booleanPref(key = "PREF_CALENDAR_ALL_DAY", default = true) var calendarAllDay by booleanPref(key = "PREF_CALENDAR_ALL_DAY", default = true)
var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "") var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "")
var nextEventId by longPref(key = "PREF_NEXT_EVENT_ID", default = -1) var nextEventId by longPref(key = "PREF_NEXT_EVENT_ID", default = -1)
var nextEventName by stringPref(key = "PREF_NEXT_EVENT_NAME")
var nextEventStartDate by longPref(key = "PREF_NEXT_EVENT_START_DATE")
var nextEventAllDay by booleanPref(key = "PREF_NEXT_EVENT_ALL_DAY")
var nextEventLocation by stringPref(key = "PREF_NEXT_EVENT_LOCATION")
var nextEventEndDate by longPref(key = "PREF_NEXT_EVENT_END_DATE")
var nextEventCalendarId by intPref(key = "PREF_NEXT_EVENT_CALENDAR_ID")
var customLocationLat by stringPref(key = "PREF_CUSTOM_LOCATION_LAT", default = "") var customLocationLat by stringPref(key = "PREF_CUSTOM_LOCATION_LAT", default = "")
var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "") var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "")
var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", default = "") var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", default = "")
var dateFormat by stringPref(default = "") var dateFormat by stringPref(default = "")
var isDateCapitalize by booleanPref(default = true)
var isDateUppercase by booleanPref(default = false)
var weatherRefreshPeriod by intPref(key = "PREF_WEATHER_REFRESH_PERIOD", default = 1) var weatherRefreshPeriod by intPref(key = "PREF_WEATHER_REFRESH_PERIOD", default = 1)
var showUntil by intPref(key = "PREF_SHOW_UNTIL", default = 1) var showUntil by intPref(key = "PREF_SHOW_UNTIL", default = 1)
var calendarAppName by stringPref(key = "PREF_CALENDAR_APP_NAME", default = "") var calendarAppName by stringPref(key = "PREF_CALENDAR_APP_NAME", default = "")
var calendarAppPackage by stringPref(key = "PREF_CALENDAR_APP_PACKAGE", default = "") var calendarAppPackage by stringPref(key = "PREF_CALENDAR_APP_PACKAGE", default = "")
var weatherAppName by stringPref(key = "PREF_WEATHER_APP_NAME", default = "") var weatherAppName by stringPref(key = "PREF_WEATHER_APP_NAME", default = "")
var weatherAppPackage by stringPref(key = "PREF_WEATHER_APP_PACKAGE", default = "") var weatherAppPackage by stringPref(key = "PREF_WEATHER_APP_PACKAGE", default = "")
var weatherProviderApi by stringPref(key = "PREF_WEATHER_PROVIDER_API_KEY", default = "") var weatherProviderApiOpen by stringPref(key = "PREF_WEATHER_PROVIDER_API_KEY", default = "")
var weatherProviderApiHere by stringPref(default = "")
var weatherProviderApiWeatherApi by stringPref(default = "")
var weatherProviderApiWeatherBit by stringPref(default = "")
var weatherProviderApiAccuweather by stringPref(default = "")
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 eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "")
var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "") var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "")
var openEventDetails by booleanPref(default = true) var openEventDetails by booleanPref(default = true)
var widgetUpdateFrequency by intPref(default = Constants.WidgetUpdateFrequency.DEFAULT.rawValue)
var textGlobalColor by stringPref(key = "PREF_TEXT_COLOR", default = "#FFFFFF") var textGlobalColor by stringPref(key = "PREF_TEXT_COLOR", default = "#FFFFFF")
var textGlobalAlpha by stringPref(default = "FF") var textGlobalAlpha by stringPref(default = "FF")
var textSecondaryColor by stringPref(default = "#FFFFFF")
var textSecondaryAlpha by stringPref(default = "FF")
var backgroundCardColor by stringPref(default = "#000000") var backgroundCardColor by stringPref(default = "#000000")
var backgroundCardAlpha by stringPref(default = "00") var backgroundCardAlpha by stringPref(default = "00")
var clockTextColor by stringPref(default = "#FFFFFF") var clockTextColor by stringPref(default = "#FFFFFF")
var clockTextAlpha by stringPref(default = "FF") var clockTextAlpha by stringPref(default = "FF")
var textGlobalColorDark by stringPref(default = "#FFFFFF")
var textGlobalAlphaDark by stringPref(default = "FF")
var textSecondaryColorDark by stringPref(default = "#FFFFFF")
var textSecondaryAlphaDark by stringPref(default = "FF")
var backgroundCardColorDark by stringPref(default = "#000000")
var backgroundCardAlphaDark by stringPref(default = "00")
var clockTextColorDark by stringPref(default = "#FFFFFF")
var clockTextAlphaDark by stringPref(default = "FF")
var showAMPMIndicator by booleanPref(default = true) var showAMPMIndicator by booleanPref(default = true)
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// UI
var widgetMargin by floatPref(default = Constants.Dimension.NONE.rawValue)
var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue)
// Clock
var altTimezoneLabel by stringPref(default = "")
var altTimezoneId by stringPref(default = "")
// Global // Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f) var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 24f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f) var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 16f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 90f) var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 72f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.value) var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.SMALL.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false) var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "") var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "") var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")
var textShadow by intPref(key = "PREF_TEXT_SHADOW", default = 1) var textShadow by intPref(key = "PREF_TEXT_SHADOW", default = 1)
var textShadowDark by intPref(default = 1)
var showDiffTime by booleanPref(key = "PREF_SHOW_DIFF_TIME", default = true) var showDiffTime by booleanPref(key = "PREF_SHOW_DIFF_TIME", default = true)
var showDeclinedEvents by booleanPref(key = "PREF_SHOW_DECLINED_EVENTS", default = false) var showDeclinedEvents by booleanPref(key = "PREF_SHOW_DECLINED_EVENTS", default = false)
var showInvitedEvents by booleanPref(default = false)
var showAcceptedEvents by booleanPref(default = true)
var showOnlyBusyEvents by booleanPref(default = false)
var secondRowInformation by intPref(key = "PREF_SECOND_ROW_INFORMATION", default = 0) var secondRowInformation by intPref(key = "PREF_SECOND_ROW_INFORMATION", default = 0)
var customFont by intPref(key = "PREF_CUSTOM_FONT", default = Constants.CUSTOM_FONT_PRODUCT_SANS) var customFont by intPref(key = "PREF_CUSTOM_FONT", default = Constants.CUSTOM_FONT_GOOGLE_SANS)
var customFontFile by stringPref(key = "PREF_CUSTOM_FONT_FILE") var customFontFile by stringPref(default = "")
var customFontName by stringPref(default = "")
var customFontVariant by stringPref(default = "regular")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true) var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showNextEventOnMultipleLines by booleanPref(default = false)
var showDividers by booleanPref(default = true) var showDividers by booleanPref(default = true)
var widgetAlign by intPref(default = Constants.WidgetAlign.CENTER.rawValue)
// Settings // Settings
var showWallpaper by booleanPref(default = true) var showWallpaper by booleanPref(default = true)
var showBigClockWarning by booleanPref(default = true)
var showWeatherWarning by booleanPref(default = true)
var showPreview by booleanPref(default = true) var showPreview by booleanPref(default = true)
var showXiaomiWarning by booleanPref(default = true) var showXiaomiWarning by booleanPref(default = true)
// Glance // Glance
var showGlance by booleanPref(default = true)
var enabledGlanceProviderOrder by stringPref(default = "") var enabledGlanceProviderOrder by stringPref(default = "")
var customNotes by stringPref(default = "") var customNotes by stringPref(default = "")
var showNextAlarm by booleanPref(default = true) var showNextAlarm by booleanPref(default = false)
var showBatteryCharging by booleanPref(default = false) var showBatteryCharging by booleanPref(default = false)
var isBatteryLevelLow by booleanPref(default = false) var isBatteryLevelLow by booleanPref(default = false)
var isCharging by booleanPref(default = false)
var googleFitSteps by longPref(default = -1) var googleFitSteps by longPref(default = -1)
var showDailySteps by booleanPref(default = false) 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 = "")
var lastNotificationIcon by intPref(default = 0)
var lastNotificationPackage by stringPref(default = "")
var showMusic by booleanPref(default = false) var showMusic by booleanPref(default = false)
var mediaInfoFormat by stringPref(default = "") var mediaInfoFormat by stringPref(default = MediaPlayerHelper.DEFAULT_MEDIA_INFO_FORMAT)
var mediaPlayerTitle by stringPref(default = "") var mediaPlayerTitle by stringPref(default = "")
var mediaPlayerAlbum by stringPref(default = "") var mediaPlayerAlbum by stringPref(default = "")
var mediaPlayerArtist by stringPref(default = "") var mediaPlayerArtist by stringPref(default = "")
var mediaPlayerPackage by stringPref(default = "") var mediaPlayerPackage by stringPref(default = IntentHelper.DO_NOTHING_OPTION)
var musicPlayersFilter by stringPref(default = "")
var appNotificationsFilter by stringPref(default = "")
var showEventsAsGlanceProvider by booleanPref(default = false)
var showWeatherAsGlanceProvider by booleanPref(default = false)
// Integrations // Integrations
var installedIntegrations by intPref(default = 0) var installedIntegrations by intPref(default = 0)

View File

@ -0,0 +1,48 @@
package com.tommasoberlose.anotherwidget.helpers
import android.content.ContentResolver
import android.content.Context
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationManagerCompat
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 {
return Preferences.lastNotificationId != -1 && Preferences.lastNotificationIcon != 0 && Preferences.lastNotificationPackage.isNotBlank() && Preferences.lastNotificationTitle.isNotBlank()
}
fun clearLastNotification(context: Context) {
Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::lastNotificationId)
remove(Preferences::lastNotificationTitle)
remove(Preferences::lastNotificationPackage)
remove(Preferences::lastNotificationIcon)
}
MainWidget.updateWidget(context)
NotificationListener.clearTimeout(context)
}
fun checkNotificationAccess(context: Context): Boolean {
val contentResolver: ContentResolver = context.contentResolver
val enabledNotificationListeners =
Settings.Secure.getString(contentResolver, "enabled_notification_listeners")
val packageName: String = context.packageName
return NotificationManagerCompat.getEnabledListenerPackages(context).contains(packageName) && (enabledNotificationListeners != null && enabledNotificationListeners.contains(NotificationListener::class.java.name))
}
fun isAppAccepted(appPkg: String): Boolean = Preferences.appNotificationsFilter == "" || Preferences.appNotificationsFilter.contains(appPkg)
fun toggleAppFilter(appPkg: String) {
if (Preferences.appNotificationsFilter == "" || !Preferences.appNotificationsFilter.contains(appPkg)) {
Preferences.appNotificationsFilter = Preferences.appNotificationsFilter.split(",").union(listOf(appPkg)).joinToString(separator = ",")
} else {
Preferences.appNotificationsFilter = Preferences.appNotificationsFilter.split(",").filter { it != appPkg }.joinToString(separator = ",")
}
}
}

View File

@ -1,9 +1,13 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.Log import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -12,9 +16,9 @@ object AlarmHelper {
val alarm = nextAlarmClock val alarm = nextAlarmClock
return if ( return if (
alarm != null alarm != null
&& alarm.triggerTime - Calendar.getInstance().timeInMillis > 10 * 60 * 1000 && alarm.triggerTime - Calendar.getInstance().timeInMillis > 5 * 60 * 1000
&& alarm.triggerTime - Calendar.getInstance().timeInMillis < 12 * 60 * 60 * 1000
) { ) {
setTimeout(context, alarm.triggerTime)
"%s %s".format( "%s %s".format(
SimpleDateFormat("EEE", Locale.getDefault()).format(alarm.triggerTime), SimpleDateFormat("EEE", Locale.getDefault()).format(alarm.triggerTime),
DateFormat.getTimeFormat(context).format(Date(alarm.triggerTime)) DateFormat.getTimeFormat(context).format(Date(alarm.triggerTime))
@ -33,4 +37,33 @@ object AlarmHelper {
) )
} }
} }
private fun setTimeout(context: Context, trigger: Long) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
setExactIfCanSchedule(
AlarmManager.RTC,
trigger,
PendingIntent.getBroadcast(
context,
ALARM_UPDATE_ID,
intent,
PendingIntent.FLAG_IMMUTABLE
)
)
}
}
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
private const val ALARM_UPDATE_ID = 24953
} }

View File

@ -0,0 +1,23 @@
package com.tommasoberlose.anotherwidget.helpers
import android.content.Context
import android.content.Context.BATTERY_SERVICE
import android.os.BatteryManager
import androidx.core.content.ContextCompat.getSystemService
import com.tommasoberlose.anotherwidget.global.Preferences
object BatteryHelper {
fun updateBatteryInfo(context: Context) {
with(context.getSystemService(BATTERY_SERVICE) as BatteryManager) {
Preferences.isBatteryLevelLow = getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) <= 15
Preferences.isCharging = isCharging
}
}
fun getBatteryLevel(context: Context): Int {
with(context.getSystemService(BATTERY_SERVICE) as BatteryManager) {
return getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
}

View File

@ -4,11 +4,9 @@ import android.content.Context
import android.graphics.* import android.graphics.*
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
object BitmapHelper { object BitmapHelper {
@ -39,15 +37,6 @@ object BitmapHelper {
1 1
} }
if (draw) {
FirebaseCrashlytics.getInstance().setCustomKey("WIDTH SPEC", measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("HEIGHT SPEC", measuredHeight)
FirebaseCrashlytics.getInstance().setCustomKey("VIEW measuredWidth", view.measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("VIEW measuredHeight", view.measuredHeight)
FirebaseCrashlytics.getInstance().setCustomKey("WIDGET final width", measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("WIDGET final height", view.measuredHeight)
}
return try { return try {
val btm = Bitmap.createBitmap( val btm = Bitmap.createBitmap(
widgetWidth, widgetWidth,
@ -58,13 +47,12 @@ object BitmapHelper {
//Bind a canvas to it //Bind a canvas to it
val canvas = Canvas(btm) val canvas = Canvas(btm)
// draw the view on the canvas // draw the view on the canvas
view.layout(0, 0, measuredWidth, measuredHeight) view.layout(0, 0, widgetWidth, widgetHeight)
view.draw(canvas) view.draw(canvas)
//return the bitmap //return the bitmap
} }
btm btm
} catch (ex: Exception) { } catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8) Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8)
} }
} }
@ -104,13 +92,12 @@ object BitmapHelper {
} }
fun drawableToBitmap(drawable: Drawable): Bitmap? { fun drawableToBitmap(drawable: Drawable): Bitmap? {
var bitmap: Bitmap? = null
if (drawable is BitmapDrawable) { if (drawable is BitmapDrawable) {
if (drawable.bitmap != null) { if (drawable.bitmap != null) {
return drawable.bitmap return drawable.bitmap
} }
} }
bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) { val bitmap: Bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) {
Bitmap.createBitmap( Bitmap.createBitmap(
1, 1,
1, 1,

View File

@ -1,21 +1,15 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.Manifest import android.Manifest
import android.content.ContentUris
import android.content.Context import android.content.Context
import android.content.Intent
import android.provider.CalendarContract import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.* import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
/** /**
@ -23,110 +17,8 @@ import kotlin.collections.ArrayList
*/ */
object CalendarHelper { object CalendarHelper {
fun updateEventList(context: Context) { fun updateEventList(context: Context) {
val eventRepository = EventRepository(context) UpdateCalendarWorker.enqueue(context)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
val now = Calendar.getInstance()
val limit = Calendar.getInstance()
when (Preferences.showUntil) {
0 -> limit.add(Calendar.HOUR, 3)
1 -> limit.add(Calendar.HOUR, 6)
2 -> limit.add(Calendar.HOUR, 12)
3 -> limit.add(Calendar.DAY_OF_MONTH, 1)
4 -> limit.add(Calendar.DAY_OF_MONTH, 3)
5 -> limit.add(Calendar.DAY_OF_MONTH, 7)
6 -> limit.add(Calendar.MINUTE, 30)
7 -> limit.add(Calendar.HOUR, 1)
else -> limit.add(Calendar.HOUR, 6)
}
val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
ContentUris.appendId(builder, now.timeInMillis)
ContentUris.appendId(builder, limit.timeInMillis)
if (!context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
} else {
try {
val provider = CalendarProvider(context)
val data = provider.getInstances(now.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 && (Preferences.calendarAllDay || !e.allDay) && !getFilteredCalendarIdList().contains(
e.calendarId
) && (Preferences.showDeclinedEvents || e.selfAttendeeStatus.toInt() != CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
) {
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(
instance.id,
e.id,
e.title ?: "",
instance.begin,
instance.end,
e.calendarId.toInt(),
e.allDay,
e.eventLocation ?: ""
)
)
}
} catch (ignored: Exception) {
}
}
}
if (eventList.isEmpty()) {
eventRepository.resetNextEventData()
} else {
eventList.sortWith(Comparator { event: Event, event1: Event ->
if (event.allDay && event1.allDay) {
event.startDate.compareTo(event1.startDate)
} else if (event.allDay) {
1
} else if (event1.allDay) {
-1
} else {
event1.startDate.compareTo(event.startDate)
}
})
eventList.reverse()
eventRepository.saveEvents(
eventList
)
eventRepository.saveNextEventData(
eventList[0]
)
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
}
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> { fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -148,7 +40,8 @@ object CalendarHelper {
} }
fun getFilteredCalendarIdList(): List<Long> { fun getFilteredCalendarIdList(): List<Long> {
return Preferences.calendarFilter.split(",").map { it.replace(" ", "") }.filter { it != "" }.map { it.toLong() } return Preferences.calendarFilter.split(",").map { it.replace(" ", "") }
.filter { it != "" }.map { it.toLong() }
} }
fun filterCalendar(list: List<Long>) { fun filterCalendar(list: List<Long>) {
@ -156,10 +49,44 @@ object CalendarHelper {
} }
fun setEventUpdatesAndroidN(context: Context) { fun setEventUpdatesAndroidN(context: Context) {
EventListenerJob.schedule(context) UpdateCalendarWorker.enqueueTrigger(context)
} }
fun removeEventUpdatesAndroidN(context: Context) { fun removeEventUpdatesAndroidN(context: Context) {
EventListenerJob.remove(context) UpdateCalendarWorker.cancelTrigger(context)
}
fun List<Event>.applyFilters() : List<Event> {
return this
.asSequence()
.filter { (Preferences.showDeclinedEvents || it.selfAttendeeStatus != CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED) }
.filter { (Preferences.showAcceptedEvents || it.selfAttendeeStatus != CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED) }
.filter { (Preferences.showInvitedEvents || it.selfAttendeeStatus != CalendarContract.Attendees.ATTENDEE_STATUS_INVITED) }
.filter { (Preferences.calendarAllDay || !it.allDay) }
.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

@ -1,78 +1,124 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color import android.graphics.Color
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toast
import kotlin.math.roundToInt import kotlin.math.roundToInt
object ColorHelper { object ColorHelper {
fun getFontColor(): Int { fun getFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format(Preferences.textGlobalAlpha, Preferences.textGlobalColor.replace("#", ""))) Color.parseColor("#%s%s".format(if (!isDark) Preferences.textGlobalAlpha else Preferences.textGlobalAlphaDark,
(if (!isDark) Preferences.textGlobalColor else Preferences.textGlobalColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#FFFFFFFF") Color.parseColor("#FFFFFFFF")
} }
} }
fun getFontColorAlpha(): Int { fun getFontColorAlpha(isDark: Boolean): Int {
return try { return try {
Preferences.textGlobalAlpha.toIntValue().toDouble() * 255 / 100 (if (!isDark) Preferences.textGlobalAlpha else Preferences.textGlobalAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) { } catch (e: Exception) {
"FF".toIntValue().toDouble() * 255 / 100 "FF".toIntValue().toDouble() * 255 / 100
}.roundToInt() }.roundToInt()
} }
fun getFontColorRgb(): Int { fun getFontColorRgb(isDark: Boolean): Int {
return try { return try {
Color.parseColor(Preferences.textGlobalColor) Color.parseColor((if (!isDark) Preferences.textGlobalColor else Preferences.textGlobalColorDark))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#000000") Color.parseColor("#000000")
} }
} }
fun getClockFontColor(): Int {
fun getSecondaryFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format(Preferences.clockTextAlpha, Preferences.clockTextColor.replace("#", ""))) Color.parseColor("#%s%s".format((if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark),
(if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark).replace(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#FFFFFFFF") Color.parseColor("#FFFFFFFF")
} }
} }
fun getClockFontColorAlpha(): Int { fun getSecondaryFontColorAlpha(isDark: Boolean): Int {
return try { return try {
Preferences.clockTextAlpha.toIntValue().toDouble() * 255 / 100 (if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) { } catch (e: Exception) {
"FF".toIntValue().toDouble() * 255 / 100 "FF".toIntValue().toDouble() * 255 / 100
}.roundToInt() }.roundToInt()
} }
fun getClockFontColorRgb(): Int { fun getSecondaryFontColorRgb(isDark: Boolean): Int {
return try { return try {
Color.parseColor(Preferences.clockTextColor) Color.parseColor((if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#000000") Color.parseColor("#000000")
} }
} }
fun getBackgroundColor(): Int { fun getClockFontColor(isDark: Boolean): Int {
return try { return try {
Color.parseColor("#%s%s".format(Preferences.backgroundCardAlpha, Preferences.backgroundCardColor.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")
}
}
fun getClockFontColorAlpha(isDark: Boolean): Int {
return try {
(if (!isDark) Preferences.clockTextAlpha else Preferences.clockTextAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) {
"FF".toIntValue().toDouble() * 255 / 100
}.roundToInt()
}
fun getClockFontColorRgb(isDark: Boolean): Int {
return try {
Color.parseColor((if (!isDark) Preferences.clockTextColor else Preferences.clockTextColorDark))
} catch (e: Exception) {
Color.parseColor("#000000")
}
}
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(
"#",
"")))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#00000000") Color.parseColor("#00000000")
} }
} }
fun getBackgroundAlpha(): Int { fun getBackgroundAlpha(isDark: Boolean): Int {
return try { return try {
Preferences.backgroundCardAlpha.toIntValue().toDouble() * 255 / 100 (if (!isDark) Preferences.backgroundCardAlpha else Preferences.backgroundCardAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) { } catch (e: Exception) {
"00".toIntValue().toDouble() * 255 / 100 "00".toIntValue().toDouble() * 255 / 100
}.roundToInt() }.roundToInt()
} }
fun getBackgroundColorRgb(): Int { fun getBackgroundColorRgb(isDark: Boolean): Int {
return try { return try {
Color.parseColor(Preferences.backgroundCardColor) Color.parseColor((if (!isDark) Preferences.backgroundCardColor else Preferences.backgroundCardColorDark))
} catch (e: Exception) { } catch (e: Exception) {
Color.parseColor("#000000") Color.parseColor("#000000")
} }
@ -98,4 +144,50 @@ object ColorHelper {
val hexValue = this.toInt(16).toDouble() val hexValue = this.toInt(16).toDouble()
return (hexValue * 100 / 255).roundToInt() return (hexValue * 100 / 255).roundToInt()
} }
fun String.isColor(): Boolean {
return try {
Color.parseColor(this)
true
} catch (iae: IllegalArgumentException) {
iae.printStackTrace()
false
}
}
@SuppressLint("DefaultLocale")
fun Context.copyToClipboard(color: Int?, alpha: Int) {
if (color == null) return toast(getString(R.string.error_copy_color))
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
try {
val colorString = Integer.toHexString(color)
val clip = "#%s%s".format(
alpha.toHexValue(),
if (colorString.length > 6) colorString.substring(2) else colorString
).toUpperCase()
setPrimaryClip(ClipData.newPlainText(clip, clip))
toast(getString(R.string.color_copied))
} catch (ex: Exception) {
ex.printStackTrace()
toast(getString(R.string.error_copy_color))
}
}
}
fun Context.isClipboardColor(): Boolean {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
return try { primaryClip?.getItemAt(0)?.text?.toString()?.isColor() ?: false } catch (ex: Exception) { false }
}
}
fun Context.pasteFromClipboard(pasteColor: (color: String, alpha: String) -> Unit) {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
primaryClip?.let {
val item = it.getItemAt(0).text.toString().replace("#", "")
val color = if (item.length > 6) item.substring(2) else item
val alpha = if (item.length > 6) item.substring(0, 2) else "00"
pasteColor("#$color", alpha)
}
}
}
} }

View File

@ -1,97 +0,0 @@
package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import com.google.android.gms.location.ActivityRecognition
import com.google.android.gms.location.ActivityTransition
import com.google.android.gms.location.ActivityTransitionRequest
import com.google.android.gms.location.DetectedActivity
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.FenceReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
object DailyStepsHelper {
fun registerFence(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION)) {
val transitions = mutableListOf<ActivityTransition>()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
val request = ActivityTransitionRequest(transitions)
// myPendingIntent is the instance of PendingIntent where the app receives callbacks.
val task = ActivityRecognition.getClient(context)
.requestActivityTransitionUpdates(
request,
PendingIntent.getBroadcast(
context,
2,
Intent(context, FenceReceiver::class.java),
0
)
)
task.addOnFailureListener { e: Exception ->
e.printStackTrace()
Preferences.showDailySteps = false
}
}
}
fun unregisterFence(context: Context) {
val task = ActivityRecognition.getClient(context)
.removeActivityTransitionUpdates(
PendingIntent.getBroadcast(
context,
2,
Intent(context, FenceReceiver::class.java),
0
)
)
task.addOnCompleteListener {
if (it.isSuccessful) {
PendingIntent.getBroadcast(
context,
2,
Intent(context, FenceReceiver::class.java),
0
).cancel()
}
}
}
}

View File

@ -2,7 +2,6 @@ package com.tommasoberlose.anotherwidget.helpers
import android.content.Context import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.getCapWordString import com.tommasoberlose.anotherwidget.utils.getCapWordString
import java.lang.Exception import java.lang.Exception
@ -12,18 +11,28 @@ import java.util.*
object DateHelper { object DateHelper {
fun getDateText(context: Context, date: Calendar): String { fun getDateText(context: Context, date: Calendar): String {
return if (Preferences.dateFormat != "") { return if (Preferences.dateFormat != "") {
try { val text = try {
SimpleDateFormat(Preferences.dateFormat, Locale.getDefault()).format(date.time) SimpleDateFormat(Preferences.dateFormat, Locale.getDefault()).format(date.time)
} catch (e: Exception) { } catch (e: Exception) {
getDefaultDateText(context, date) getDefaultDateText(context, date)
} }
when {
Preferences.isDateUppercase -> text.toUpperCase(Locale.getDefault())
Preferences.isDateCapitalize -> text.getCapWordString()
else -> text
}
} else { } else {
val flags: Int = val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
"%s, %s".format( val text = "%s, %s".format(
SimpleDateFormat("EEEE", Locale.getDefault()).format(date.time), SimpleDateFormat("EEEE", Locale.getDefault()).format(date.time),
DateUtils.formatDateTime(context, date.timeInMillis, flags) DateUtils.formatDateTime(context, date.timeInMillis, flags)
).getCapWordString() )
when {
Preferences.isDateUppercase -> text.toUpperCase(Locale.getDefault())
Preferences.isDateCapitalize -> text.getCapWordString()
else -> text
}
} }
} }
@ -33,6 +42,6 @@ object DateHelper {
return "%s, %s".format( return "%s, %s".format(
SimpleDateFormat("EEEE", Locale.getDefault()).format(date.time), SimpleDateFormat("EEEE", Locale.getDefault()).format(date.time),
DateUtils.formatDateTime(context, date.timeInMillis, flags) DateUtils.formatDateTime(context, date.timeInMillis, flags)
).getCapWordString() )
} }
} }

View File

@ -1,19 +1,28 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.content.Context import android.content.Context
import android.util.Log
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.GlanceProvider import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.checkIfFitInstalled
import java.util.ArrayList import java.util.ArrayList
object GlanceProviderHelper { object GlanceProviderHelper {
fun getGlanceProviders(): ArrayList<Constants.GlanceProviderId> { fun getGlanceProviders(context: Context): ArrayList<Constants.GlanceProviderId> {
val enabledProviders = Preferences.enabledGlanceProviderOrder.split(",").filter { it != "" } val enabledProviders = Preferences.enabledGlanceProviderOrder.split(",").filter { it != "" }
val providers = Constants.GlanceProviderId.values()
providers.sortWith(Comparator { p1, p2 -> val providers = Constants.GlanceProviderId.values()
.filter {
context.checkIfFitInstalled() || it != Constants.GlanceProviderId.GOOGLE_FIT_STEPS
}
.toTypedArray()
return ArrayList(providers.filter { enabledProviders.contains(it.id) }.sortedWith(Comparator { p1, p2 ->
when { when {
enabledProviders.contains(p1.id) && enabledProviders.contains(p2.id) -> { enabledProviders.contains(p1.id) && enabledProviders.contains(p2.id) -> {
enabledProviders.indexOf(p1.id).compareTo(enabledProviders.indexOf(p2.id)) enabledProviders.indexOf(p1.id).compareTo(enabledProviders.indexOf(p2.id))
@ -28,9 +37,7 @@ object GlanceProviderHelper {
p1.id.compareTo(p2.id) p1.id.compareTo(p2.id)
} }
} }
}) }) + providers.filter { !enabledProviders.contains(it.id) })
return ArrayList(providers.toList())
} }
fun getGlanceProviderById(context: Context, providerId: Constants.GlanceProviderId): GlanceProvider? { fun getGlanceProviderById(context: Context, providerId: Constants.GlanceProviderId): GlanceProvider? {
@ -38,47 +45,82 @@ object GlanceProviderHelper {
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> { Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_next_alarm_title), context.getString(R.string.settings_show_next_alarm_title),
R.drawable.round_alarm R.drawable.round_access_alarm_24
) )
} }
Constants.GlanceProviderId.PLAYING_SONG -> { Constants.GlanceProviderId.PLAYING_SONG -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_show_music_title), context.getString(R.string.settings_show_music_title),
R.drawable.round_music_note R.drawable.round_music_note_24
) )
} }
Constants.GlanceProviderId.CUSTOM_INFO -> { Constants.GlanceProviderId.CUSTOM_INFO -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_custom_notes_title), context.getString(R.string.settings_custom_notes_title),
R.drawable.round_notes R.drawable.round_sticky_note_2_24
) )
} }
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> { Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_low_battery_level_title), context.getString(R.string.settings_low_battery_level_title),
R.drawable.round_battery_charging_full R.drawable.round_battery_charging_full_24
) )
} }
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> { Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
GlanceProvider(providerId.id, GlanceProvider(providerId.id,
context.getString(R.string.settings_daily_steps_title), context.getString(R.string.settings_daily_steps_title),
R.drawable.round_directions_walk R.drawable.round_favorite_border_24
) )
} }
Constants.GlanceProviderId.NOTIFICATIONS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_notifications_title),
R.drawable.round_notifications_24
)
}
Constants.GlanceProviderId.GREETINGS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_greetings_title),
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
)
}
Constants.GlanceProviderId.WEATHER -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_weather_as_glance_provider_title),
R.drawable.round_brightness_5_24
)
}
} }
} }
fun saveGlanceProviderOrder(list: ArrayList<Constants.GlanceProviderId>) { fun saveGlanceProviderOrder(list: List<Constants.GlanceProviderId>) {
Preferences.enabledGlanceProviderOrder = list.joinToString(separator = ",") Preferences.enabledGlanceProviderOrder = list.joinToString(separator = ",")
} }
fun showGlanceProviders(context: Context): Boolean { fun showGlanceProviders(context: Context): Boolean {
return Preferences.showGlance && EventRepository(context).getEventsCount() == 0 && ( val eventRepository = EventRepository(context)
BatteryHelper.updateBatteryInfo(context)
val showGlance = (eventRepository.getEventsCount() == 0 || !Preferences.showEvents || Preferences.showEventsAsGlanceProvider)
&& (
(Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) ||
(Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") || (Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") ||
(MediaPlayerHelper.isSomeonePlaying(context)) || (MediaPlayerHelper.isSomeonePlaying(context)) ||
(Preferences.isBatteryLevelLow) || (Preferences.showBatteryCharging && Preferences.isCharging || Preferences.isBatteryLevelLow) ||
(Preferences.customNotes.isNotEmpty()) || (Preferences.customNotes.isNotEmpty()) ||
(Preferences.showDailySteps && Preferences.googleFitSteps > 0) (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") ||
) (Preferences.showDailySteps && Preferences.googleFitSteps > 0) ||
(Preferences.showGreetings && GreetingsHelper.showGreetings()) ||
(Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && eventRepository.getNextEvent() != null)
)
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
},
PendingIntent.FLAG_IMMUTABLE)
)
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
},
PendingIntent.FLAG_IMMUTABLE)
)
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
},
PendingIntent.FLAG_IMMUTABLE)
)
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
},
PendingIntent.FLAG_IMMUTABLE)
)
} 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
}, PendingIntent.FLAG_IMMUTABLE))
}
}
}
}
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,87 @@
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 -> 8f * 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 -> 0.8f * factor
}))
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()
colorMatrixScript.destroy()
blurScript.destroy()
//rs.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

@ -1,5 +1,6 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.ComponentName import android.content.ComponentName
import android.content.ContentUris import android.content.ContentUris
@ -11,14 +12,29 @@ import android.provider.AlarmClock
import android.provider.CalendarContract import android.provider.CalendarContract
import android.provider.CalendarContract.Events import android.provider.CalendarContract.Events
import android.util.Log import android.util.Log
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast
import java.util.* import java.util.*
object IntentHelper { object IntentHelper {
const val DEFAULT_OPTION = ""
const val DO_NOTHING_OPTION = "DO_NOTHING"
const val REFRESH_WIDGET_OPTION = "REFRESH_WIDGET"
fun getPendingIntent(context: Context, requestCode: Int, intent: Intent, flags: Int): PendingIntent {
return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
PendingIntent.getActivity(context, requestCode, intent, flags)
else
PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
fun getWidgetUpdateIntent(context: Context): Intent { fun getWidgetUpdateIntent(context: Context): Intent {
val widgetManager = AppWidgetManager.getInstance(context) val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java) val widgetComponent = ComponentName(context, MainWidget::class.java)
@ -29,33 +45,41 @@ object IntentHelper {
} }
} }
private fun getWidgetRefreshIntent(context: Context): Intent {
return Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_REFRESH
}
}
fun getGoogleMapsIntentFromAddress(context: Context, address: String): Intent { fun getGoogleMapsIntentFromAddress(context: Context, address: String): Intent {
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=$address") val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=${Uri.encode(address)}")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.`package` = "com.google.android.apps.maps" //mapIntent.`package` = "com.google.android.apps.maps"
return if (mapIntent.resolveActivity(context.packageManager) != null) { return if (mapIntent.resolveActivity(context.packageManager) != null) {
mapIntent mapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} else { } else {
val map = "http://maps.google.co.in/maps?q=$address" val map = "https://www.google.com/maps/search/?api=1&query=${Uri.encode(address)}"
val i = Intent(Intent.ACTION_VIEW, Uri.parse(map)); Intent(Intent.ACTION_VIEW, Uri.parse(map)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
i
} }
} }
fun getWeatherIntent(context: Context): Intent { fun getWeatherIntent(context: Context): Intent {
return when (Preferences.weatherAppPackage) { return when (Preferences.weatherAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity") data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline") component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
setClassName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
@ -64,44 +88,42 @@ object IntentHelper {
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
} catch (e: Exception) { } catch (e: Exception) {
Intent(Intent.ACTION_VIEW).apply { Intent()
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")
}
} }
} }
} }
} }
fun getCalendarIntent(context: Context): Intent { fun getCalendarIntent(context: Context, time: Long? = null): Intent {
val calendarUri = CalendarContract.CONTENT_URI val calendarUri = CalendarContract.CONTENT_URI
.buildUpon() .buildUpon()
.appendPath("time") .appendPath("time")
.appendPath(Calendar.getInstance().timeInMillis.toString()) .appendPath((time ?: Calendar.getInstance().timeInMillis).toString())
.build() .build()
return when (Preferences.calendarAppPackage) { return when (Preferences.calendarAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = calendarUri data = calendarUri
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
pm.getLaunchIntentForPackage(Preferences.calendarAppPackage)!!.apply { pm.getLaunchIntentForPackage(Preferences.calendarAppPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Intent.ACTION_VIEW action = Intent.ACTION_VIEW
data = calendarUri data = calendarUri
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() Intent()
Intent(Intent.ACTION_VIEW).apply {
data = calendarUri
}
} }
} }
} }
@ -114,38 +136,73 @@ object IntentHelper {
if (Preferences.calendarAppPackage == "") { if (Preferences.calendarAppPackage == "") {
Intent(Intent.ACTION_VIEW).apply { Intent(Intent.ACTION_VIEW).apply {
data = uri data = uri
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate) flags = (Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate) if (!e.allDay) {
// putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, if (e.allDay) 1 else 0) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
// type = "vnd.android.cursor.item/event" putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate)
} else {
val start = Calendar.getInstance().apply {
timeInMillis = e.startDate
}
val end = Calendar.getInstance().apply {
timeInMillis = e.endDate
}
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate + start.timeZone.getOffset(start.timeInMillis))
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate + end.timeZone.getOffset(end.timeInMillis))
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, 1)
}
} }
} else { } else {
getCalendarIntent(context).apply { val calendarIntent = getCalendarIntent(context)
action = Intent.ACTION_VIEW if (calendarIntent.action == Intent.ACTION_VIEW) {
data = uri calendarIntent.apply {
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate) data = uri
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate) if (!e.allDay) {
// putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, if (e.allDay) 1 else 0) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
// type = "vnd.android.cursor.item/event" putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate)
} else {
val start = Calendar.getInstance().apply {
timeInMillis = e.startDate
}
val end = Calendar.getInstance().apply {
timeInMillis = e.endDate
}
putExtra(
CalendarContract.EXTRA_EVENT_BEGIN_TIME,
start.timeInMillis + start.timeZone.getOffset(start.timeInMillis)
)
putExtra(
CalendarContract.EXTRA_EVENT_END_TIME,
end.timeInMillis + end.timeZone.getOffset(end.timeInMillis)
)
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, 1)
}
}
} else {
Intent()
} }
} }
} }
false -> { false -> {
getCalendarIntent(context) getCalendarIntent(context, e.startDate)
} }
} }
} }
fun getClockIntent(context: Context): Intent { fun getClockIntent(context: Context): Intent {
return when (Preferences.clockAppPackage) { return when (Preferences.clockAppPackage) {
"" -> { DEFAULT_OPTION -> {
Intent(AlarmClock.ACTION_SHOW_ALARMS).apply { Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
} }
"_" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
REFRESH_WIDGET_OPTION -> {
getWidgetRefreshIntent(context)
}
else -> { else -> {
val pm: PackageManager = context.packageManager val pm: PackageManager = context.packageManager
try { try {
@ -153,17 +210,19 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER) addCategory(Intent.CATEGORY_LAUNCHER)
} }
} catch (e: Exception) { } catch (e: Exception) {
Intent(AlarmClock.ACTION_SHOW_ALARMS).apply { Intent()
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} }
} }
} }
} }
fun getBatteryIntent(): Intent {
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
fun getMusicIntent(context: Context): Intent { fun getMusicIntent(context: Context): Intent {
return when (Preferences.mediaPlayerPackage) { return when (Preferences.mediaPlayerPackage) {
"" -> { DO_NOTHING_OPTION -> {
Intent() Intent()
} }
else -> { else -> {
@ -171,6 +230,7 @@ object IntentHelper {
try { try {
pm.getLaunchIntentForPackage(Preferences.mediaPlayerPackage)!!.apply { pm.getLaunchIntentForPackage(Preferences.mediaPlayerPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER) addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
} catch (e: Exception) { } catch (e: Exception) {
Intent() Intent()
@ -178,4 +238,28 @@ object IntentHelper {
} }
} }
} }
fun getFitIntent(context: Context): Intent {
val pm: PackageManager = context.packageManager
return try {
pm.getLaunchIntentForPackage("com.google.android.apps.fitness")!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
}
}
fun getNotificationIntent(context: Context): Intent {
val pm: PackageManager = context.packageManager
return try {
pm.getLaunchIntentForPackage(Preferences.lastNotificationPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
}
}
} }

View File

@ -1,40 +1,62 @@
package com.tommasoberlose.anotherwidget.helpers package com.tommasoberlose.anotherwidget.helpers
import android.app.Notification
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.media.MediaMetadata import android.media.MediaMetadata
import android.media.session.MediaController import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.MediaSessionManager import android.media.session.MediaSessionManager
import android.media.session.PlaybackState import android.media.session.PlaybackState
import android.util.Log import com.chibatching.kotpref.Kotpref
import androidx.core.app.NotificationManagerCompat import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.MusicNotificationListener import com.tommasoberlose.anotherwidget.receivers.NotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import java.lang.Exception import java.lang.Exception
object MediaPlayerHelper { object MediaPlayerHelper {
fun isSomeonePlaying(context: Context) = Preferences.showMusic && NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName) && Preferences.mediaPlayerTitle != "" const val MEDIA_INFO_TITLE = "%TITLE"
const val MEDIA_INFO_ARTIST = "%ARTIST"
const val MEDIA_INFO_ALBUM = "%ALBUM"
fun getMediaInfo(): String { const val DEFAULT_MEDIA_INFO_FORMAT = "%TITLE, %ARTIST"
return if (Preferences.mediaPlayerArtist == "") {
Preferences.mediaPlayerTitle fun isSomeonePlaying(context: Context) = Preferences.showMusic && ActiveNotificationsHelper.checkNotificationAccess(context) && 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) { fun updatePlayingMediaInfo(context: Context) {
if (NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName)) { Kotpref.init(context)
if (ActiveNotificationsHelper.checkNotificationAccess(context)) {
val list = try { val list = try {
(context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager).getActiveSessions( (context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager).getActiveSessions(
ComponentName(context.packageName, MusicNotificationListener::class.java.name) ComponentName(context.packageName, NotificationListener::class.java.name)
) )
} catch (ex: Exception) { } catch (ex: Exception) {
emptyList<MediaController>() emptyList<MediaController>()
}.filter {
Preferences.musicPlayersFilter == "" || isMusicPlayerAccepted(it.packageName)
} }
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
@ -48,15 +70,24 @@ object MediaPlayerHelper {
isSomeonePlaying = true isSomeonePlaying = true
if (metadata != null) { if (metadata != null) {
Preferences.bulk { Preferences.bulk {
mediaPlayerTitle = ignoreExceptions {
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)?.toString() mediaPlayerTitle =
?: "" metadata.getText(MediaMetadata.METADATA_KEY_TITLE)
mediaPlayerArtist = ?.toString()
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)?.toString() ?: ""
?: "" }
mediaPlayerAlbum = ignoreExceptions {
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)?.toString() mediaPlayerArtist =
?: "" metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)
?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)
?.toString()
?: ""
}
} }
} }
@ -65,24 +96,35 @@ object MediaPlayerHelper {
} }
if (!isSomeonePlaying) { if (!isSomeonePlaying) {
removeMediaInfo() removeMediaInfo(context)
} }
} else { } else {
removeMediaInfo() removeMediaInfo(context)
} }
} else { } else {
removeMediaInfo() removeMediaInfo(context)
} }
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
} }
private fun removeMediaInfo() { private fun removeMediaInfo(context: Context) {
Preferences.bulk { Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::mediaPlayerTitle) remove(Preferences::mediaPlayerTitle)
remove(Preferences::mediaPlayerArtist) remove(Preferences::mediaPlayerArtist)
remove(Preferences::mediaPlayerAlbum) remove(Preferences::mediaPlayerAlbum)
remove(Preferences::mediaPlayerPackage) remove(Preferences::mediaPlayerPackage)
} }
} }
fun isMusicPlayerAccepted(appPkg: String): Boolean = Preferences.musicPlayersFilter == "" || Preferences.musicPlayersFilter.contains(appPkg)
fun toggleMusicPlayerFilter(appPkg: String) {
if (Preferences.musicPlayersFilter == "" || !Preferences.musicPlayersFilter.contains(appPkg)) {
Preferences.musicPlayersFilter = Preferences.musicPlayersFilter.split(",").union(listOf(appPkg)).joinToString(separator = ",")
} else {
Preferences.musicPlayersFilter = Preferences.musicPlayersFilter.split(",").filter { it != appPkg }.joinToString(separator = ",")
}
}
} }

View File

@ -3,6 +3,8 @@ package com.tommasoberlose.anotherwidget.helpers
import android.content.Context import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -52,25 +54,58 @@ object SettingsStringHelper {
} }
} }
fun getCustomFontLabel(shadow: Int): Int { fun getCustomFontLabel(context: Context, font: Int): String {
return when (shadow) { return when (font) {
0 -> R.string.custom_font_subtitle_0 Constants.CUSTOM_FONT_GOOGLE_SANS -> context.getString(R.string.custom_font_subtitle_1) + " - ${getVariantLabel(context, Preferences.customFontVariant)}"
1 -> R.string.custom_font_subtitle_1 Constants.CUSTOM_FONT_DOWNLOADED -> Preferences.customFontName + " - ${getVariantLabel(context, Preferences.customFontVariant)}"
else -> R.string.custom_font_subtitle_1 else -> context.getString(R.string.custom_font_subtitle_0)
} }
} }
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("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)
variant.contains("800") -> context.getString(R.string.font_800)
variant.contains("900") -> context.getString(R.string.font_900)
else -> context.getString(R.string.font_400)
}
fun getDifferenceText(context: Context, now: Long, start: Long): String { fun getDifferenceText(context: Context, now: Long, start: Long): String {
val nowDate = DateTime(now) val nowDate = DateTime(now)
val eventDate = DateTime(start) val eventDate = DateTime(start)
val difference = start - now
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
when { when {
difference <= 0 || TimeUnit.MILLISECONDS.toHours(difference) < 1 -> { difference <= 0 -> {
return "" return ""
} }
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 15 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.LOW.rawValue -> {
return context.getString(R.string.soon)
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 -> {
return context.getString(R.string.now)
}
TimeUnit.MILLISECONDS.toHours(difference) < 12 -> { TimeUnit.MILLISECONDS.toHours(difference) < 12 -> {
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.HOUR_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString() return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.HOUR_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
} }
@ -91,9 +126,6 @@ object SettingsStringHelper {
val nowDate = DateTime(now) val nowDate = DateTime(now)
val eventDate = DateTime(start) val eventDate = DateTime(start)
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
return when (eventDate.dayOfYear) { return when (eventDate.dayOfYear) {
nowDate.dayOfYear -> { nowDate.dayOfYear -> {
"" ""

View File

@ -2,16 +2,15 @@ package com.tommasoberlose.anotherwidget.helpers
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.os.Build import com.chibatching.kotpref.Kotpref
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import org.greenrobot.eventbus.EventBus import com.tommasoberlose.anotherwidget.utils.isDarkTheme
/** /**
@ -20,112 +19,488 @@ import org.greenrobot.eventbus.EventBus
object WeatherHelper { object WeatherHelper {
fun updateWeather(context: Context) { fun updateWeather(context: Context, force: Boolean = false) {
val networkApi = WeatherNetworkApi(context) if (Preferences.showWeather || force)
if (Preferences.customLocationAdd != "") { WeatherWorker.enqueue(context, replace = force)
networkApi.updateWeather() else {
} else if (context.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION)) { removeWeather(context)
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener { task -> org.greenrobot.eventbus.EventBus.getDefault().post(
if (task.isSuccessful) { com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
val location = task.result )
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
networkApi.updateWeather()
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
} }
} }
fun removeWeather(context: Context) { fun removeWeather(context: Context) {
Preferences.remove(Preferences::weatherTemp) Preferences.remove(Preferences::weatherTemp)
Preferences.remove(Preferences::weatherRealTempUnit) Preferences.remove(Preferences::weatherRealTempUnit)
Preferences.remove(Preferences::weatherIcon)
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
} }
fun getWeatherIconResource(icon: String): Int { fun getProviderName(context: Context, provider: Constants.WeatherProvider = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
when (icon) { return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.settings_weather_provider_open_weather
Constants.WeatherProvider.WEATHER_BIT -> R.string.settings_weather_provider_weatherbit
Constants.WeatherProvider.WEATHER_API -> R.string.settings_weather_provider_weather_api
Constants.WeatherProvider.HERE -> R.string.settings_weather_provider_here
Constants.WeatherProvider.ACCUWEATHER -> R.string.settings_weather_provider_accuweather
Constants.WeatherProvider.WEATHER_GOV -> R.string.settings_weather_provider_weather_gov
Constants.WeatherProvider.YR -> R.string.settings_weather_provider_yr
})
}
fun getProviderInfoTitle(context: Context, provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.weather_provider_info_open_weather_title
Constants.WeatherProvider.WEATHER_BIT -> R.string.weather_provider_info_weatherbit_title
Constants.WeatherProvider.WEATHER_API -> R.string.weather_provider_info_weatherapi_title
Constants.WeatherProvider.HERE -> R.string.weather_provider_info_here_title
Constants.WeatherProvider.ACCUWEATHER -> R.string.weather_provider_info_accuweather_title
Constants.WeatherProvider.WEATHER_GOV -> R.string.weather_provider_info_weather_gov_title
Constants.WeatherProvider.YR -> R.string.weather_provider_info_yr_title
else -> R.string.nothing
})
}
fun getProviderInfoSubtitle(context: Context, provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.weather_provider_info_open_weather_subtitle
Constants.WeatherProvider.WEATHER_BIT -> R.string.weather_provider_info_weatherbit_subtitle
Constants.WeatherProvider.WEATHER_API -> R.string.weather_provider_info_weatherapi_subtitle
Constants.WeatherProvider.HERE -> R.string.weather_provider_info_here_subtitle
Constants.WeatherProvider.ACCUWEATHER -> R.string.weather_provider_info_accuweather_subtitle
Constants.WeatherProvider.WEATHER_GOV -> R.string.weather_provider_info_weather_gov_subtitle
Constants.WeatherProvider.YR -> R.string.weather_provider_info_yr_subtitle
else -> R.string.nothing
})
}
fun getProviderLink(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> "https://home.openweathermap.org/users/sign_in"
Constants.WeatherProvider.WEATHER_BIT -> "https://www.weatherbit.io/account/login"
Constants.WeatherProvider.WEATHER_API -> "https://www.weatherapi.com/login.aspx"
Constants.WeatherProvider.HERE -> "https://developer.here.com/login"
Constants.WeatherProvider.ACCUWEATHER -> "https://developer.accuweather.com/user/login"
Constants.WeatherProvider.WEATHER_GOV -> "http://www.weather.gov/"
Constants.WeatherProvider.YR -> "https://www.yr.no/"
else -> ""
}
}
fun isKeyRequired(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): Boolean = when (provider) {
Constants.WeatherProvider.OPEN_WEATHER,
Constants.WeatherProvider.WEATHER_BIT,
Constants.WeatherProvider.WEATHER_API,
Constants.WeatherProvider.HERE,
Constants.WeatherProvider.ACCUWEATHER -> true
Constants.WeatherProvider.WEATHER_GOV,
Constants.WeatherProvider.YR -> false
else -> true
}
fun getApiKey(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String = when (provider) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather
Constants.WeatherProvider.WEATHER_GOV -> ""
Constants.WeatherProvider.YR -> ""
else -> ""
}
fun getWeatherIconResource(context: Context, icon: String, style: Int = Preferences.weatherIconPack): Int {
return when (icon) {
"01d" -> { "01d" -> {
return R.drawable.clear_day when (style) {
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" -> { "02d" -> {
return R.drawable.partly_cloudy when (style) {
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" -> { "03d" -> {
return R.drawable.mostly_cloudy when (style) {
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" -> { "04d" -> {
return R.drawable.cloudy_weather when (style) {
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" -> { "09d" -> {
return R.drawable.storm_weather_day when (style) {
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" -> { "10d" -> {
return R.drawable.rainy_day when (style) {
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" -> { "11d" -> {
return R.drawable.thunder_day when (style) {
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" -> { "13d" -> {
return R.drawable.snow_day when (style) {
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" -> { "50d" -> {
return R.drawable.haze_day when (style) {
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" -> { "80d" -> {
return R.drawable.windy_day when (style) {
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" -> { "81d" -> {
return R.drawable.rain_snow_day when (style) {
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" -> { "82d" -> {
return R.drawable.haze_weather when (style) {
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
}
} }
"01n" -> { "01n" -> {
return R.drawable.clear_night when (style) {
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" -> { "02n" -> {
return R.drawable.partly_cloudy_night when (style) {
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" -> { "03n" -> {
return R.drawable.mostly_cloudy_night when (style) {
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" -> { "04n" -> {
return R.drawable.cloudy_weather when (style) {
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" -> { "09n" -> {
return R.drawable.storm_weather_night when (style) {
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" -> { "10n" -> {
return R.drawable.rainy_night when (style) {
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" -> { "11n" -> {
return R.drawable.thunder_night when (style) {
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" -> { "13n" -> {
return R.drawable.snow_night when (style) {
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" -> { "50n" -> {
return R.drawable.haze_night when (style) {
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" -> { "80n" -> {
return R.drawable.windy_night when (style) {
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" -> { "81n" -> {
return R.drawable.rain_snow_night when (style) {
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" -> { "82n" -> {
return R.drawable.haze_weather when (style) {
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
}
} }
else -> { else -> {
return R.drawable.unknown return if (context.isDarkTheme()) R.drawable.unknown_dark else R.drawable.unknown_light
} }
} }
} }
}
fun getWeatherLabel(context: Context, icon: String): String {
return when (icon) {
"01d", "01n" -> context.getString(R.string.weather_label_clear)
"02d", "02n" -> context.getString(R.string.weather_label_partly_cloudy)
"03d", "03n" -> context.getString(R.string.weather_label_mostly_cloudy)
"04d", "04n" -> context.getString(R.string.weather_label_cloudy_weather)
"09d", "09n" -> context.getString(R.string.weather_label_storm_weather)
"10d", "10n" -> context.getString(R.string.weather_label_rainy)
"11d", "11n" -> context.getString(R.string.weather_label_thunder)
"13d", "13n" -> context.getString(R.string.weather_label_snow)
"50d", "50n", "82d", "82n" -> context.getString(R.string.weather_label_haze)
"80d", "80n" -> context.getString(R.string.weather_label_windy)
"81d", "81n" -> context.getString(R.string.weather_label_rain_snow)
else -> context.getString(R.string.weather_label_unknown)
}
}
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (
iconString.substringBefore('?').substringAfterLast('/').substringBefore(',')
) {
"skc" -> "01"
"few" -> "02"
"sct" -> "02"
"bkn" -> "03"
"ovc" -> "04"
"wind_skc" -> "01"
"wind_few" -> "02"
"wind_sct" -> "02"
"wind_bkn" -> "03"
"wind_ovc" -> "04"
"snow" -> "13"
"rain_snow" -> "81"
"rain_sleet" -> "81"
"snow_sleet" -> "81"
"fzra" -> "81"
"rain_fzra" -> "81"
"snow_fzra" -> "81"
"sleet" -> "81"
"rain" -> "10"
"rain_showers" -> "10"
"rain_showers_hi" -> "10"
"tsra" -> "09"
"tsra_sct" -> "11"
"tsra_hi" -> "11"
"tornado" -> "80"
"hurricane" -> "80"
"tropical_storm" -> "09"
"dust" -> "50"
"smoke" -> "50"
"haze" -> "50"
"hot" -> "01"
"cold" -> "13"
"blizzard" -> "13"
"fog" -> "82"
else -> ""
} + if (isDaytime) "d" else "n"
fun getWeatherBitIcon(iconString: String): String = when (iconString.substring(0, 3)) {
"t01" -> "11"
"t02" -> "11"
"t03" -> "09"
"t04" -> "11"
"t05" -> "11"
"d01" -> "10"
"d02" -> "10"
"d03" -> "10"
"r01" -> "10"
"r02" -> "10"
"r03" -> "10"
"f01" -> "10"
"r04" -> "10"
"r05" -> "10"
"r06" -> "10"
"s01" -> "13"
"s02" -> "13"
"s03" -> "13"
"s04" -> "81"
"s05" -> "81"
"s06" -> "13"
"a01" -> "50"
"a02" -> "50"
"a03" -> "50"
"a04" -> "50"
"a05" -> "82"
"a06" -> "82"
"c01" -> "01"
"c02" -> "02"
"c03" -> "03"
"c04" -> "04"
else -> ""
} + iconString.substring(3)
fun getWeatherApiIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1000 -> "01"
1003 -> "02"
1006 -> "03"
1009 -> "04"
1030 -> "50"
1063 -> "10"
1066 -> "13"
1069 -> "81"
1072 -> "81"
1087 -> "11"
1114 -> "13"
1117 -> "13"
1135 -> "82"
1147 -> "82"
1150 -> "10"
1153 -> "10"
1168 -> "10"
1171 -> "10"
1180 -> "10"
1183 -> "10"
1186 -> "10"
1189 -> "10"
1192 -> "10"
1195 -> "10"
1198 -> "81"
1201 -> "81"
1204 -> "81"
1207 -> "81"
1210 -> "13"
1213 -> "13"
1216 -> "13"
1219 -> "13"
1222 -> "13"
1225 -> "13"
1237 -> "13"
1240 -> "10"
1243 -> "10"
1246 -> "10"
1249 -> "81"
1252 -> "81"
1255 -> "13"
1258 -> "13"
1261 -> "13"
1264 -> "13"
1273 -> "11"
1276 -> "09"
1279 -> "13"
1282 -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
fun getYRIcon(iconCode: String): String = when (iconCode.substringBefore('_')) {
"clearsky" -> "01"
"cloudy" -> "04"
"fair" -> "02"
"fog" -> "82"
"heavyrain" -> "10"
"heavyrainandthunder" -> "09"
"heavyrainshowers" -> "10"
"heavyrainshowersandthunder" -> "09"
"heavysleet" -> "81"
"heavysleetandthunder" -> "81"
"heavysleetshowers" -> "81"
"heavysleetshowersandthunder" -> "81"
"heavysnow" -> "13"
"heavysnowandthunder" -> "13"
"heavysnowshowers" -> "13"
"heavysnowshowersandthunder" -> "13"
"lightrain" -> "10"
"lightrainandthunder" -> "11"
"lightrainshowers" -> "10"
"lightrainshowersandthunder" -> "11"
"lightsleet" -> "81"
"lightsleetandthunder" -> "81"
"lightsleetshowers" -> "81"
"lightsnow" -> "13"
"lightsnowandthunder" -> "13"
"lightsnowshowers" -> "13"
"lightssleetshowersandthunder" -> "81"
"lightssnowshowersandthunder" -> "81"
"partlycloudy" -> "03"
"rain" -> "10"
"rainandthunder" -> "11"
"rainshowers" -> "10"
"rainshowersandthunder" -> "11"
"sleet" -> "81"
"sleetandthunder" -> "81"
"sleetshowers" -> "81"
"sleetshowersandthunder" -> "81"
"snow" -> "13"
"snowandthunder" -> "13"
"snowshowers" -> "13"
"snowshowersandthunder" -> "13"
else -> ""
} + if (iconCode.substringAfter('_', "day") == "day") "d" else "n"
}

View File

@ -3,8 +3,12 @@ package com.tommasoberlose.anotherwidget.helpers
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.content.res.Configuration.ORIENTATION_PORTRAIT
import com.google.firebase.crashlytics.FirebaseCrashlytics import android.graphics.Typeface
import com.tommasoberlose.anotherwidget.db.EventRepository import android.os.Handler
import android.os.HandlerThread
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
object WidgetHelper { object WidgetHelper {
@ -14,29 +18,23 @@ object WidgetHelper {
) { ) {
fun getWidgetsSize(widgetId: Int): Pair<Int, Int> { fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
val isPortrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT val portrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT
val width = getWidgetWidth(isPortrait, widgetId) val width = getWidgetSizeInDp(
val height = getWidgetHeight(isPortrait, widgetId) widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
)
val height = getWidgetSizeInDp(
widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
)
val widthInPx = context.dip(width) val widthInPx = context.dip(width)
val heightInPx = context.dip(height) val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
FirebaseCrashlytics.getInstance().setCustomKey("heightInPx", heightInPx)
return widthInPx to heightInPx return widthInPx to heightInPx
} }
private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int = private fun getWidgetWidth(widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
if (isPortrait) {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
} else {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
}
private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int = private fun getWidgetHeight(widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
if (isPortrait) {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
} else {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
}
private fun getWidgetSizeInDp(widgetId: Int, key: String): Int = private fun getWidgetSizeInDp(widgetId: Int, key: String): Int =
appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0) appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0)
@ -53,4 +51,39 @@ object WidgetHelper {
width to second * factor width to second * factor
} }
} }
fun runWithCustomTypeface(context: Context, function: (typeface: Typeface?) -> Unit) {
if (Preferences.customFontFile != "") {
val request = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
Preferences.customFontFile,
R.array.com_google_android_gms_fonts_certs
)
val handlerThread = HandlerThread("generateView")
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
handlerThread.quit()
function.invoke(typeface)
}
override fun onTypefaceRequestFailed(reason: Int) {
handlerThread.quit()
function.invoke(null)
}
}
handlerThread.start()
//if (Looper.myLooper() == null) {
// Looper.prepare()
//}
Handler(handlerThread.looper).run {
FontsContractCompat.requestFont(context, request, callback, this)
}
} else {
function.invoke(null)
}
}
} }

View File

@ -1,21 +1,35 @@
package com.tommasoberlose.anotherwidget.models package com.tommasoberlose.anotherwidget.models
import io.realm.RealmObject import android.provider.CalendarContract
import java.util.Date import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/** /**
* Created by tommaso on 05/10/17. * Created by tommaso on 05/10/17.
*/ */
open class Event(var id: Long = 0, @Entity(tableName = "events")
var eventID: Long = 0, data class Event(
var title: String = "", @PrimaryKey
var startDate: Long = 0, val id: Long = 0,
var endDate: Long = 0, @ColumnInfo(name = "event_id")
var calendarID: Int = 0, val eventID: Long = 0,
var allDay: Boolean = false, val title: String = "",
var address: String = "") : RealmObject() { @ColumnInfo(name = "start_date")
val startDate: Long = 0,
@ColumnInfo(name = "end_date")
val endDate: Long = 0,
@ColumnInfo(name = "calendar_id")
val calendarID: Long = 0,
@ColumnInfo(name = "all_day")
val allDay: Boolean = false,
val address: String = "",
@ColumnInfo(name = "self_attendee_status")
val selfAttendeeStatus: Int = CalendarContract.Attendees.ATTENDEE_STATUS_NONE,
val availability: Int = CalendarContract.EventsEntity.AVAILABILITY_BUSY
)/* {
override fun toString(): String { override fun toString(): String {
return "Event:\nEVENT ID: " + eventID + "\nTITLE: " + title + "\nSTART DATE: " + Date(startDate) + "\nEND DATE: " + Date(endDate) + "\nCAL ID: " + calendarID + "\nADDRESS: " + address return "Event:\nEVENT ID: " + eventID + "\nTITLE: " + title + "\nSTART DATE: " + Date(startDate) + "\nEND DATE: " + Date(endDate) + "\nCAL ID: " + calendarID + "\nADDRESS: " + address
} }
} }*/

View File

@ -0,0 +1,38 @@
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 {
id = response.body["timezoneId"] as String
} catch(ex: Exception) {
ex.printStackTrace()
}
}
}
return id
}
}

View File

@ -1,42 +1,531 @@
package com.tommasoberlose.anotherwidget.network package com.tommasoberlose.anotherwidget.network
import android.content.Context 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
import com.kwabenaberko.openweathermaplib.constants.Units import com.kwabenaberko.openweathermaplib.constants.Units
import com.kwabenaberko.openweathermaplib.implementation.OpenWeatherMapHelper import com.kwabenaberko.openweathermaplib.implementation.OpenWeatherMapHelper
import com.kwabenaberko.openweathermaplib.implementation.callbacks.CurrentWeatherCallback import com.kwabenaberko.openweathermaplib.implementation.callbacks.CurrentWeatherCallback
import com.kwabenaberko.openweathermaplib.models.currentweather.CurrentWeather import com.kwabenaberko.openweathermaplib.models.currentweather.CurrentWeather
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.repository.*
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import java.lang.Exception
import java.text.SimpleDateFormat
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
class WeatherNetworkApi(val context: Context) { class WeatherNetworkApi(val context: Context) {
fun updateWeather() { suspend fun updateWeather() {
if (Preferences.showWeather && Preferences.weatherProviderApi != "" && Preferences.customLocationLat != "" && Preferences.customLocationLon != "") { Kotpref.init(context)
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApi) Preferences.weatherProviderError = "-"
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC) Preferences.weatherProviderLocationError = ""
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback {
override fun onSuccess(currentWeather: CurrentWeather?) {
currentWeather?.let {
Preferences.weatherTemp = currentWeather.main.temp.toFloat()
Preferences.weatherIcon = currentWeather.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent()) if (Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
} when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
} Constants.WeatherProvider.OPEN_WEATHER -> useOpenWeatherMap(context)
Constants.WeatherProvider.WEATHER_GOV -> useWeatherGov(context)
override fun onFailure(throwable: Throwable?) { Constants.WeatherProvider.WEATHER_BIT -> useWeatherBitProvider(context)
} Constants.WeatherProvider.WEATHER_API -> useWeatherApiProvider(context)
Constants.WeatherProvider.HERE -> useHereProvider(context)
}) Constants.WeatherProvider.ACCUWEATHER -> useAccuweatherProvider(context)
Constants.WeatherProvider.YR -> useYrProvider(context)
}
} else { } else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
WeatherHelper.removeWeather( WeatherHelper.removeWeather(
context context
) )
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
} }
}
private suspend fun useOpenWeatherMap(context: Context) {
if (Preferences.weatherProviderApiOpen != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen)
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC)
when (val response = suspendCancellableCoroutine<Any?> { continuation ->
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback {
override fun onSuccess(currentWeather: CurrentWeather?) {
continuation.resume(currentWeather)
}
override fun onFailure(throwable: Throwable?) {
continuation.resume(throwable)
}
})
}) {
is CurrentWeather -> {
Preferences.weatherTemp = response.main.temp.toFloat()
Preferences.weatherIcon = response.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
is Throwable -> {
if (response.javaClass == Throwable::class.java) {
// server error, see [OpenWeatherMapHelper.handleCurrentWeatherResponse]
if (response.message?.startsWith("UnAuthorized") == true) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
WeatherHelper.removeWeather(
context
)
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherGov(context: Context) {
val repository = WeatherGovRepository()
val pointsResponse = executeWithRetry(times = 5) {
repository.getGridPoints(
Preferences.customLocationLat,
Preferences.customLocationLon
)
}
when (pointsResponse) {
is NetworkResponse.Success -> {
try {
val pp = pointsResponse.body["properties"] as LinkedTreeMap<*, *>
val gridId = pp["gridId"] as String
val gridX = pp["gridX"] as Double
val gridY = pp["gridY"] as Double
when (val weatherResponse = repository.getWeather(
gridId,
gridX,
gridY,
if (Preferences.weatherTempUnit == "F") "us" else "si"
)) {
is NetworkResponse.Success -> {
try {
val props =
weatherResponse.body["properties"] as LinkedTreeMap<*, *>
val periods = props["periods"] as List<*>
@android.annotation.SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
"yyyy-MM-dd'T'HH:mm:ssXXX"
else
"yyyy-MM-dd'T'HH:mm:ssZ"
)
for (period in periods) {
val now = period as LinkedTreeMap<*, *>
val endTime = format.parse(now["endTime"] as String)!!
if (endTime.time > System.currentTimeMillis()) {
val temp = now["temperature"] as Double
val fullIcon = now["icon"] as String
val isDaytime = now["isDaytime"] as Boolean
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherGovIcon(fullIcon, isDaytime)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
break
}
}
} catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (pointsResponse.code) {
404 -> {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_wrong_location)
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
private suspend fun useHereProvider(context: Context) {
if (Preferences.weatherProviderApiHere != "") {
val repository = HereRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val observations = response.body["observations"] as LinkedTreeMap<*, *>
val location = (observations["location"] as List<*>).first() as LinkedTreeMap<*, *>
val observation = (location["observation"] as List<*>).first() as LinkedTreeMap<*, *>
val iconName = observation["iconName"] as String
val daylight = observation["daylight"] as String
val temperature = observation["temperature"] as String
Preferences.weatherTemp = temperature.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(iconName, daylight != "N")
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherBitProvider(context: Context) {
if (Preferences.weatherProviderApiWeatherBit != "") {
val repository = WeatherbitRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val data = response.body["data"] as List<*>?
data?.first()?.let { it as LinkedTreeMap<*, *>
val temp = it["temp"] as Double
val weatherInfo = it["weather"] as LinkedTreeMap<*, *>
val iconCode = weatherInfo["icon"] as String
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherBitIcon(iconCode)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
403 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherApiProvider(context: Context) {
if (Preferences.weatherProviderApiWeatherApi != "") {
val repository = WeatherApiRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val current = response.body["current"] as LinkedTreeMap<*, *>?
current?.let {
val tempC = current["temp_c"] as Double
val tempF = current["temp_f"] as Double
val isDay = current["is_day"] as Double
val condition = current["condition"] as LinkedTreeMap<*, *>
val iconCode = condition["code"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherApiIcon(iconCode.toInt(), isDay.toInt() == 1)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
403 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_expired_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") {
val repository = AccuweatherRepository()
when (val locationResponse = repository.getLocation()) {
is NetworkResponse.Success -> {
try {
val key = locationResponse.body["Key"] as String
when (val weatherResponse = repository.getWeather(key)) {
is NetworkResponse.Success -> {
try {
weatherResponse.body.first().let {
val temp = it["Temperature"] as LinkedTreeMap<*, *>
val tempC = (temp["Metric"] as LinkedTreeMap<*, *>)["Value"] as Double
val tempF = (temp["Imperial"] as LinkedTreeMap<*, *>)["Value"] as Double
val isDay = it["IsDayTime"] as Boolean
val icon = it["WeatherIcon"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(icon.toInt(), isDay)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (locationResponse.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
503 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_expired_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useYrProvider(context: Context) {
val repository = YrRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val pp = response.body["properties"] as LinkedTreeMap<*, *>
val data = pp["timeseries"] as List<*>?
data?.first()?.let { it as LinkedTreeMap<*, *>
val dd = it["data"] as LinkedTreeMap<*, *>
val instant = dd["instant"] as LinkedTreeMap<*, *>
val next = dd["next_1_hours"] as LinkedTreeMap<*, *>
val details = instant["details"] as LinkedTreeMap<*, *>
val temp = details["air_temperature"] as Double
val summary = next["summary"] as LinkedTreeMap<*, *>
val iconCode = summary["symbol_code"] as String
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getYRIcon(iconCode)
Preferences.weatherTempUnit = "C"
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
} catch(ex: Exception) {
ex.printStackTrace()
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
}

View File

@ -0,0 +1,87 @@
package com.tommasoberlose.anotherwidget.network.api
import com.haroldadmin.cnradapter.NetworkResponse
import retrofit2.http.*
object ApiServices {
interface WeatherGovApiService {
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("points/{latitude},{longitude}")
suspend fun getGridPoints(
@Path("latitude") latitude: String,
@Path("longitude") longitude: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("gridpoints/{gridId}/{gridX},{gridY}/forecast/hourly")
suspend fun getWeather(
@Path("gridId") gridId: String,
@Path("gridX") gridX: Int,
@Path("gridY") gridY: Int,
@Query("units") unit: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface WeatherBitService {
@GET("current")
suspend fun getWeather(
@Query("key") key: String,
@Query("lat") lat: String,
@Query("lon") lon: String,
@Query("units") units: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface WeatherApiService {
@Headers("Accept: application/json")
@GET("current.json")
suspend fun getWeather(
@Query("key") key: String,
@Query("q") location: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface HereService {
@GET("report.json")
suspend fun getWeather(
@Query("apiKey") apiKey: String,
@Query("latitude") latitude: String,
@Query("longitude") longitude: String,
@Query("product") product: String,
@Query("oneobservation") oneobservation: Boolean,
@Query("metric") metric: Boolean,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface AccuweatherService {
@GET("locations/v1/cities/geoposition/search")
suspend fun getLocation(
@Query("apikey") apikey: String,
@Query("q") location: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@GET("currentconditions/v1/{locationKey}")
suspend fun getWeather(
@Path("locationKey") locationKey: String,
@Query("apikey") apikey: String
): NetworkResponse<List<HashMap<String, Any>>, HashMap<String, Any>>
}
interface YrService {
@Headers("User-Agent: AnotherWidget")
@GET("compact.json")
suspend fun getWeather(
@Query("lat") lat: String,
@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,43 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class AccuweatherRepository {
/* ACCUWEATHER */
private val apiServiceAccu: ApiServices.AccuweatherService = getRetrofit().create(ApiServices.AccuweatherService::class.java)
suspend fun getLocation() = apiServiceAccu.getLocation(Preferences.weatherProviderApiAccuweather, "${Preferences.customLocationLat},${Preferences.customLocationLon}")
suspend fun getWeather(locationKey: String) = apiServiceAccu.getWeather(locationKey, Preferences.weatherProviderApiAccuweather)
companion object {
private const val BASE_URL_ACCU = "https://dataservice.accuweather.com/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_ACCU)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
fun getWeatherIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1, 2, 30, 33, 34 -> "01"
3, 4, 35, 36 -> "02"
5, 37 -> "50"
6, 38 -> "03"
7, 8 -> "04"
11 -> "82"
12, 13, 14, 18, 39, 40 -> "10"
15 -> "09"
16, 17, 41, 42 -> "11"
32 -> "80"
19, 20, 21, 22, 23, 24, 31, 43, 44 -> "13"
25, 26, 29 -> "81"
else -> ""
} + if (isDaytime) "d" else "n"
}

View File

@ -0,0 +1,179 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class HereRepository {
/* HERE */
private val apiServiceHere: ApiServices.HereService = getRetrofit().create(ApiServices.HereService::class.java)
suspend fun getWeather() = apiServiceHere.getWeather(Preferences.weatherProviderApiHere, Preferences.customLocationLat, Preferences.customLocationLon, "observation", true, Preferences.weatherTempUnit != "F")
companion object {
private const val BASE_URL_HERE = "https://weather.ls.hereapi.com/weather/1.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_HERE)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
fun getWeatherIcon(iconName: String, isDaytime: Boolean): String = when(iconName.substringAfter("night_")) {
"sunny" -> "01"
"clear" -> "01"
"mostly_sunny" -> "01"
"mostly_clear" -> "01"
"passing_clounds" -> "02"
"more_sun_than_clouds" -> "02"
"scattered_clouds" -> "02"
"partly_cloudy" -> "02"
"a_mixture_of_sun_and_clouds" -> "03"
"increasing_cloudiness" -> "03"
"breaks_of_sun_late" -> "03"
"afternoon_clouds" -> "03"
"morning_clouds" -> "03"
"partly_sunny" -> "03"
"high_level_clouds" -> "03"
"decreasing_cloudiness" -> "03"
"clearing_skies" -> "01"
"high_clouds" -> "03"
"rain_early" -> "10"
"heavy_rain_early" -> "10"
"strong_thunderstorms" -> "09"
"severe_thunderstorms" -> "09"
"thundershowers" -> "11"
"thunderstorms" -> "11"
"tstorms_early" -> "11"
"isolated_tstorms_late" -> "11"
"scattered_tstorms_late" -> "11"
"tstorms_late" -> "11"
"tstorms" -> "11"
"ice_fog" -> "82"
"more_clouds_than_sun" -> "03"
"broken_clouds" -> "03"
"scattered_showers" -> "10"
"a_few_showers" -> "10"
"light_showers" -> "10"
"passing_showers" -> "10"
"rain_showers" -> "10"
"showers" -> "10"
"widely_scattered_tstorms" -> "11"
"isolated_tstorms" -> "11"
"a_few_tstorms" -> "11"
"scattered_tstorms" -> "11"
"hazy_sunshine" -> "50"
"haze" -> "50"
"smoke" -> "50"
"low_level_haze" -> "50"
"early_fog_followed_by_sunny_skies" -> "50"
"early_fog" -> "82"
"light_fog" -> "82"
"fog" -> "82"
"dense_fog" -> "82"
//"night_haze"
//"night_smoke"
//"night_low_level_haze"
//"night_widely_scattered_tstorms"
//"night_isolated_tstorms"
//"night_a_few_tstorms"
//"night_scattered_tstorms"
//"night_tstorms"
//"night_clear"
"mostly_cloudy" -> "03"
"cloudy" -> "04"
"overcast" -> "04"
"low_clouds" -> "03"
"hail" -> "10"
"sleet" -> "81"
"light_mixture_of_precip" -> "81"
"icy_mix" -> "81"
"mixture_of_precip" -> "81"
"heavy_mixture_of_precip" -> "81"
"snow_changing_to_rain" -> "81"
"snow_changing_to_an_icy_mix" -> "81"
"an_icy_mix_changing_to_snow" -> "81"
"an_icy_mix_changing_to_rain" -> "81"
"rain_changing_to_snow" -> "81"
"rain_changing_to_an_icy_mix" -> "81"
"light_icy_mix_early" -> "81"
"icy_mix_early" -> "81"
"light_icy_mix_late" -> "81"
"icy_mix_late" -> "81"
"snow_rain_mix" -> "81"
"scattered_flurries" -> "13"
"snow_flurries" -> "13"
"light_snow_showers" -> "13"
"snow_showers" -> "13"
"light_snow" -> "13"
"flurries_early" -> "13"
"snow_showers_early" -> "13"
"light_snow_early" -> "13"
"flurries_late" -> "13"
"snow_showers_late" -> "13"
"light_snow_late" -> "13"
//"night_decreasing_cloudiness"
//"night_clearing_skies"
//"night_high_level_clouds"
//"night_high_clouds"
//"night_scattered_showers"
//"night_a_few_showers"
//"night_light_showers"
//"night_passing_showers"
//"night_rain_showers"
//"night_sprinkles"
//"night_showers"
//"night_mostly_clear"
//"night_passing_clouds"
//"night_scattered_clouds"
//"night_partly_cloudy"
//"increasing_cloudiness"
//"night_afternoon_clouds"
//"night_morning_clouds"
//"night_broken_clouds"
//"night_mostly_cloudy"
"light_freezing_rain" -> "81"
"freezing_rain" -> "81"
"heavy_rain" -> "10"
"lots_of_rain" -> "10"
"tons_of_rain" -> "10"
//"heavy_rain_early" -> "10"
"heavy_rain_late" -> "10"
"flash_floods" -> "10"
"flood" -> "10"
"drizzle" -> "10"
"sprinkles" -> "10"
"light_rain" -> "10"
"sprinkles_early" -> "10"
"light_rain_early" -> "10"
"sprinkles_late" -> "10"
"light_rain_late" -> "10"
"rain" -> "10"
"numerous_showers" -> "10"
"showery" -> "10"
"showers_early" -> "10"
//"rain_early" -> "10"
"showers_late" -> "10"
"rain_late" -> "10"
"snow" -> "13"
"moderate_snow" -> "13"
"snow_early" -> "13"
"snow_late" -> "13"
"heavy_snow" -> "13"
"heavy_snow_early" -> "13"
"heavy_snow_late" -> "13"
"tornado" -> "80"
"tropical_storm" -> "09"
"hurricane" -> "80"
"sandstorm" -> "50"
"duststorm" -> "50"
"snowstorm" -> "13"
"blizzard" -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
}

View File

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

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class WeatherApiRepository {
/* WEATHER API*/
private val apiServiceApi: ApiServices.WeatherApiService = getRetrofit().create(ApiServices.WeatherApiService::class.java)
suspend fun getWeather() = apiServiceApi.getWeather(Preferences.weatherProviderApiWeatherApi, "${Preferences.customLocationLat},${Preferences.customLocationLon}")
companion object {
private const val BASE_URL_API = "http://api.weatherapi.com/v1/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_API)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
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 WeatherGovRepository {
/* WEATHER GOV*/
private val apiServiceGov: ApiServices.WeatherGovApiService = getRetrofit().create(ApiServices.WeatherGovApiService::class.java)
suspend fun getGridPoints(latitude: String, longitude: String) = apiServiceGov.getGridPoints(latitude, longitude)
suspend fun getWeather(gridId: String, gridX: Double, gridY: Double, unit: String) = apiServiceGov.getWeather(gridId, gridX.toInt(), gridY.toInt(), unit)
companion object {
private const val BASE_URL_GOV = "https://api.weather.gov/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_GOV)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class WeatherbitRepository {
/* BIT */
private val apiServiceBit: ApiServices.WeatherBitService = getRetrofit().create(ApiServices.WeatherBitService::class.java)
suspend fun getWeather() = apiServiceBit.getWeather(Preferences.weatherProviderApiWeatherBit, Preferences.customLocationLat, Preferences.customLocationLon, if (Preferences.weatherTempUnit == "F") "I" else "M")
companion object {
private const val BASE_URL_BIT = "https://api.weatherbit.io/v2.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_BIT)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class YrRepository {
/* YR */
private val apiServiceYr: ApiServices.YrService = getRetrofit().create(ApiServices.YrService::class.java)
suspend fun getWeather() = apiServiceYr.getWeather(Preferences.customLocationLat, Preferences.customLocationLon)
companion object {
private const val BASE_URL_YR = "https://api.met.no/weatherapi/locationforecast/2.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_YR)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -4,14 +4,18 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.BatteryManager import android.os.BatteryManager
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast
class BatteryLevelReceiver : BroadcastReceiver() { class BatteryLevelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when(intent.action) { when(intent.action) {
Intent.ACTION_BATTERY_LOW -> Preferences.isBatteryLevelLow = true Intent.ACTION_BATTERY_LOW -> Preferences.isBatteryLevelLow = true
Intent.ACTION_BATTERY_OKAY -> Preferences.isBatteryLevelLow = false Intent.ACTION_BATTERY_OKAY -> Preferences.isBatteryLevelLow = false
Intent.ACTION_POWER_CONNECTED -> Preferences.isCharging = true
Intent.ACTION_POWER_DISCONNECTED -> Preferences.isCharging = false
} }
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
} }

View File

@ -1,32 +0,0 @@
package com.tommasoberlose.anotherwidget.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Actions
import java.lang.Exception
class CrashlyticsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_REPORT_CRASH) {
val exception: Exception = intent.getSerializableExtra(EXCEPTION) as Exception
FirebaseCrashlytics.getInstance().recordException(exception)
FirebaseCrashlytics.getInstance().sendUnsentReports()
}
}
companion object {
private const val EXCEPTION = "EXCEPTION"
fun sendCrash(context: Context, exception: Exception) {
context.sendBroadcast(Intent(context, CrashlyticsReceiver::class.java).apply {
action = Actions.ACTION_REPORT_CRASH
putExtra(EXCEPTION, exception)
})
}
}
}

View File

@ -1,84 +0,0 @@
package com.tommasoberlose.anotherwidget.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions
import com.google.android.gms.fitness.data.DataSource
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.data.Field
import com.google.android.gms.fitness.data.Field.FIELD_STEPS
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.location.ActivityTransitionResult
import com.google.android.gms.location.DetectedActivity
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import java.util.*
import java.util.concurrent.TimeUnit
class FenceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ActivityTransitionResult.hasResult(intent)) {
val result = ActivityTransitionResult.extractResult(intent)!!
val lastEvent = result.transitionEvents.last()
if (lastEvent.activityType == DetectedActivity.WALKING || lastEvent.activityType == DetectedActivity.RUNNING) {
requestDailySteps(context)
} else {
resetDailySteps()
}
}
}
private fun resetDailySteps() {
Preferences.googleFitSteps = -1
}
companion object {
val FITNESS_OPTIONS = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.build()
fun requestDailySteps(context: Context) {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (account != null && GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
val cal: Calendar = Calendar.getInstance()
cal.set(Calendar.HOUR_OF_DAY, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
val startTime: Long = cal.timeInMillis
cal.add(Calendar.DAY_OF_YEAR, 1)
val endTime: Long = cal.timeInMillis
val readRequest = DataReadRequest.Builder()
.aggregate(
DataType.TYPE_STEP_COUNT_DELTA,
DataType.AGGREGATE_STEP_COUNT_DELTA
)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.bucketByTime(1, TimeUnit.DAYS)
.build()
Fitness.getHistoryClient(context, account)
.readData(readRequest)
.addOnSuccessListener { response ->
Preferences.googleFitSteps = response.buckets.sumBy {
it.getDataSet(DataType.AGGREGATE_STEP_COUNT_DELTA)?.dataPoints?.get(0)?.getValue(FIELD_STEPS)?.asInt() ?: 0
}.toLong()
MainWidget.updateWidget(context)
}
}
}
}
}

View File

@ -1,37 +0,0 @@
package com.tommasoberlose.anotherwidget.receivers
import android.app.Notification
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.WidgetHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
class MusicNotificationListener : NotificationListenerService() {
override fun onListenerConnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
super.onListenerConnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.notification?.extras?.let { bundle ->
bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let {
MediaPlayerHelper.updatePlayingMediaInfo(this)
}
}
super.onNotificationPosted(sbn)
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
MediaPlayerHelper.updatePlayingMediaInfo(this)
super.onNotificationRemoved(sbn)
}
}

View File

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

View File

@ -0,0 +1,118 @@
package com.tommasoberlose.anotherwidget.receivers
import android.app.*
import android.content.Context
import android.content.Intent
import android.media.session.MediaSession
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 com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.lang.Exception
import java.util.*
class NotificationListener : NotificationListenerService() {
override fun onListenerConnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerConnected()
}
override fun onListenerDisconnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.notification?.extras?.let { bundle ->
bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
} ?: run {
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
if (Preferences.showNotifications && 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) ?: ""
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)
}
}
}
super.onNotificationPosted(sbn)
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
sbn?.let {
if (Preferences.showNotifications && sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
ActiveNotificationsHelper.clearLastNotification(this)
}
}
super.onNotificationRemoved(sbn)
}
private fun setTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter)
if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) {
setExactIfCanSchedule(
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 -> 60 * 1000
},
PendingIntent.getBroadcast(
context,
5,
intent,
PendingIntent.FLAG_IMMUTABLE
)
)
}
}
}
companion object {
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 5, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
}
}

View File

@ -1,22 +0,0 @@
package com.tommasoberlose.anotherwidget.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class PlayerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("ciao", "player ok")
// val cmd = intent.getStringExtra("command")
// Log.v("tag ", "$action / $cmd")
// val artist = intent.getStringExtra("artist")
// val album = intent.getStringExtra("album")
// val track = intent.getStringExtra("track")
// Log.v("tag", "$artist:$album:$track")
}
}

View File

@ -5,16 +5,16 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.core.app.AlarmManagerCompat
import androidx.core.content.ContextCompat.getSystemService
import com.tommasoberlose.anotherwidget.db.EventRepository import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import org.joda.time.Period import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.util.* import java.util.*
import org.joda.time.Period
class UpdatesReceiver : BroadcastReceiver() { class UpdatesReceiver : BroadcastReceiver() {
@ -25,18 +25,41 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED, Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIME_CHANGED, Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED, Intent.ACTION_LOCALE_CHANGED -> {
Actions.ACTION_CALENDAR_UPDATE -> CalendarHelper.updateEventList(context) CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
ActiveNotificationsHelper.clearLastNotification(context)
GreetingsHelper.toggleGreetings(context)
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
Intent.ACTION_DATE_CHANGED, Intent.ACTION_DATE_CHANGED,
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED, Actions.ACTION_CALENDAR_UPDATE -> {
CalendarHelper.updateEventList(context)
}
Actions.ACTION_TIME_UPDATE -> { Actions.ACTION_TIME_UPDATE -> {
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) { if (intent.hasExtra(EVENT_ID)) {
setUpdates(context, intent.getLongExtra(EVENT_ID, -1)) setUpdates(context, intent.getLongExtra(EVENT_ID, -1))
} }
} }
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context)
}
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
}
Actions.ACTION_REFRESH -> {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context)
}
} }
} }
@ -44,68 +67,131 @@ class UpdatesReceiver : BroadcastReceiver() {
const val EVENT_ID = "EVENT_ID" const val EVENT_ID = "EVENT_ID"
fun setUpdates(context: Context, eventId: Long? = null) { fun setUpdates(context: Context, eventId: Long? = null) {
if (!Preferences.showEvents)
return
val eventRepository = EventRepository(context) val eventRepository = EventRepository(context)
if (eventId == null) { if (eventId == null) {
removeUpdates(context) // schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactIfCanSchedule(
AlarmManager.RTC,
Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis,
PendingIntent.getBroadcast(
context,
0,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
},
PendingIntent.FLAG_IMMUTABLE
)
)
}
eventRepository.getEvents().forEach { event -> eventRepository.getFutureEvents().forEach { event ->
setEventUpdate(context, event) setEventUpdate(context, event)
} }
} else { } else {
val event = eventRepository.getEventByEventId(eventId) val event = eventRepository.getEventById(eventId)
if (event != null) { if (event != null) {
setEventUpdate(context, event) setEventUpdate(context, event)
} }
} }
eventRepository.close()
} }
private fun setEventUpdate(context: Context, event: Event) { private fun setEventUpdate(context: Context, event: Event) {
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate, org.joda.time.PeriodType.time())
val limit = when (Preferences.showUntil) {
0 -> 1000 * 60 * 60 * 3
1 -> 1000 * 60 * 60 * 6
2 -> 1000 * 60 * 60 * 12
3 -> 1000 * 60 * 60 * 24
4 -> 1000 * 60 * 60 * 24 * 3
5 -> 1000 * 60 * 60 * 24 * 7
6 -> 1000 * 60 * 30
7 -> 1000 * 60 * 60
else -> 1000 * 60 * 60 * 6
}
val fireTime = when {
event.startDate <= now.timeInMillis
-> event.endDate
event.startDate > now.timeInMillis + limit
-> event.startDate - limit
!Preferences.showDiffTime
-> return
event.allDay
-> event.startDate
diff.hours > 12
-> event.startDate - 12 * 1000 * 60 * 60 + 1000 * 60
diff.hours > 0
-> event.startDate - diff.hours * 1000 * 60 * 60 + 1000 * 60
else
-> event.startDate - 1000 * 60 * when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
when {
diff.minutes >= 45 -> 44
diff.minutes >= 30 -> 29
diff.minutes >= 15 -> 14
else -> 0
}
}
Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
when {
diff.minutes >= 5 -> diff.minutes - diff.minutes % 5 - 1
else -> 0
}
}
else -> 0
}
}
// no need to schedule updates after the next ACTION_CALENDAR_UPDATE
if (Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis <= fireTime) return
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val now = Calendar.getInstance().apply { setExactIfCanSchedule(
set(Calendar.SECOND, 0) AlarmManager.RTC,
set(Calendar.MILLISECOND, 0) fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60),
} PendingIntent.getBroadcast(
val diff = Period(now.timeInMillis, event.startDate) context,
if (event.startDate > now.timeInMillis) { event.id.toInt(),
// Update the widget every hour till the event Intent(context, UpdatesReceiver::class.java).apply {
setExactAndAllowWhileIdle( action = Actions.ACTION_TIME_UPDATE
AlarmManager.RTC, if (event.startDate > now.timeInMillis)
if (event.startDate - diff.hours * 1000 * 60 * 60 > (now.timeInMillis + 120 * 1000)) event.startDate - diff.hours * 1000 * 60 * 60 else now.timeInMillis + 120000, putExtra(EVENT_ID, event.id)
PendingIntent.getBroadcast( },
context, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
0
)
) )
} else { )
// Update the widget one second after the event is finished
val fireTime =
if (event.endDate > now.timeInMillis + 120 * 1000) event.endDate else now.timeInMillis + 120000
setExactAndAllowWhileIdle(
AlarmManager.RTC,
fireTime,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
},
0
)
)
}
} }
} }
fun removeUpdates(context: Context) { fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
EventRepository(context).getEvents().forEach { cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply {
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java), 0)) action = Actions.ACTION_CALENDAR_UPDATE
}, PendingIntent.FLAG_IMMUTABLE))
val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.id.toInt(), Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
}, PendingIntent.FLAG_IMMUTABLE))
} }
eventRepository.close()
} }
} }
} }

View File

@ -1,16 +1,12 @@
package com.tommasoberlose.anotherwidget.receivers package com.tommasoberlose.anotherwidget.receivers
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import java.util.* import com.tommasoberlose.anotherwidget.services.WeatherWorker
class WeatherReceiver : BroadcastReceiver() { class WeatherReceiver : BroadcastReceiver() {
@ -20,8 +16,7 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED, Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED, Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED -> setUpdates(context) Intent.ACTION_TIME_CHANGED,
Actions.ACTION_WEATHER_UPDATE -> { Actions.ACTION_WEATHER_UPDATE -> {
WeatherHelper.updateWeather(context) WeatherHelper.updateWeather(context)
} }
@ -29,52 +24,14 @@ class WeatherReceiver : BroadcastReceiver() {
} }
companion object { companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) { fun setUpdates(context: Context) {
removeUpdates(context) if (Preferences.showWeather) {
WeatherWorker.enqueueTrigger(context)
if (Preferences.showWeather && Preferences.weatherProviderApi != "") {
val interval = MINUTE * when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setRepeating(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis,
interval,
PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
}
fun setOneTimeUpdate(context: Context) {
if (Preferences.showWeather && Preferences.weatherProviderApi != "") {
listOf(10, 20, 30).forEach {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactAndAllowWhileIdle(
AlarmManager.RTC,
it * MINUTE,
PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
} }
} }
fun removeUpdates(context: Context) { fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { WeatherWorker.cancelTrigger(context)
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
listOf(10, 20, 30).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
}
}
} }
} }
} }

View File

@ -6,6 +6,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.IntentHelper import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.utils.toast import com.tommasoberlose.anotherwidget.utils.toast
@ -15,19 +16,25 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) { if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) {
try { try {
context.startActivity(IntentHelper.getWeatherIntent(context)) IntentHelper.getWeatherIntent(context).run {
if (flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(this)
else
context.sendBroadcast(this)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
val uri = Uri.parse("https://yandex.ru/pogoda")
.buildUpon()
.appendQueryParameter("lat", Preferences.customLocationLat)
.appendQueryParameter("lon", Preferences.customLocationLon)
.build()
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try { try {
context.applicationContext.startActivity(IntentHelper.getWeatherIntent(context.applicationContext)) context.startActivity(i)
} catch (e: Exception) { } catch (ignored: Exception) {
val uri = Uri.parse("http://www.google.com/#q=weather") context.toast(context.getString(R.string.error_opening_app))
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try {
context.startActivity(i)
} catch (ignored: Exception) {
context.toast(context.getString(R.string.error_opening_app))
}
} }
} }
} }

View File

@ -1,55 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobInfo.TriggerContentUri
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
class EventListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
CalendarHelper.updateEventList(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val jobId = 1005
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(jobId, componentName)
.addTriggerContentUri(TriggerContentUri(
CalendarContract.CONTENT_URI,
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
))
.build()
)
}
}
}
fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(jobId)
}
}
}

View File

@ -0,0 +1,199 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.*
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
class UpdateCalendarWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val context = applicationContext
UpdatesReceiver.removeUpdates(context)
val eventRepository = EventRepository(context)
if (Preferences.showEvents) {
if (!context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
val limit = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
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)
}
}
try {
val eventList = ArrayList<Event>()
val provider = CalendarProvider(context)
// apply time zone offset to correctly fetch all-day events
val data = provider.getInstances(
now.timeInMillis + now.timeZone.getOffset(now.timeInMillis).coerceAtMost(0),
limit.timeInMillis + limit.timeZone.getOffset(limit.timeInMillis).coerceAtLeast(0)
)
if (data != null) {
val filteredCalendarIdList = CalendarHelper.getFilteredCalendarIdList()
for (instance in data.list) {
try {
val e = provider.getEvent(instance.eventId)
if (e == null || e.deleted || filteredCalendarIdList.contains(e.calendarId))
continue
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end) {
/* Following check may result in "fake" all-day events with
* non-UTC start/end time, and therefore cannot be found by
* Calendar when tapped to open details.
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
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,
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
val first = filteredEventList.first()
if (Preferences.nextEventId != first.id && (
//Preferences.showWeatherAsGlanceProvider || !Preferences.showNextEvent ||
eventRepository.getEventById(first.id)?.startDate != first.startDate))
eventRepository.saveNextEventData(first)
eventRepository.saveEvents(filteredEventList)
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
}
eventRepository.close()
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
if (Preferences.showEvents)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventList",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventListTrigger",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<Trigger>().setConstraints(
Constraints.Builder().addContentUriTrigger(
CalendarContract.CONTENT_URI,
true
).build()
).build()
)
}
}
fun cancelTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateEventListTrigger"
)
}
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showEvents && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -0,0 +1,93 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class WeatherWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val context = applicationContext
if (Preferences.customLocationAdd == "" &&
context.checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
) {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
var location: Location? = null
for (provider in lm.getProviders(true)) {
lm.getLastKnownLocation(provider)?.let {
if (location == null ||
it.time - location!!.time > 2 * 60 * 1000 ||
(it.time - location!!.time > -2 * 60 * 1000 && it.accuracy < location!!.accuracy))
location = it
}
}
location?.let { location ->
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
}
withContext(Dispatchers.IO) {
WeatherNetworkApi(context).updateWeather()
}
if (Preferences.showWeather)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context, replace: Boolean = false) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeather",
if (replace) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<WeatherWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
val interval = when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeatherTrigger",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<Trigger>().setInitialDelay(
interval, TimeUnit.MINUTES
).build()
)
}
fun cancelTrigger(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateWeatherTrigger"
)
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showWeather && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -1,135 +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.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.list_view
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
viewModel.appList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
})
}
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()
}
}
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,124 +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.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.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.openURI
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
viewModel.dateInput.observe(this, Observer { dateFormat ->
formatJob?.cancel()
formatJob = lifecycleScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
loader.visibility = View.VISIBLE
}
delay(200)
val text = if (dateFormat != "") {
try {
SimpleDateFormat(dateFormat, Locale.getDefault()).format(DATE.time)
} catch (e: Exception) {
ERROR_STRING
}
} else {
"__"
}
withContext(Dispatchers.Main) {
action_save.isVisible = text != ERROR_STRING
loader.visibility = View.INVISIBLE
date_format_value.text = text
}
}
})
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
action_save.setOnClickListener {
Preferences.dateFormat = viewModel.dateInput.value ?: ""
finish()
}
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,161 +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.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.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.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) {
requirePermission()
}
}
.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
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
}
}
})
}
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()
}
}
}

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

View File

@ -0,0 +1,28 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityMainBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import kotlinx.coroutines.delay
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenResumed {
delay(1000)
if (!this@SplashActivity.isDestroyed) {
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}
}
}

View File

@ -1,93 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
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.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.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
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)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<SkuDetails>(R.layout.inapp_product_layout) { item, injector ->
injector
.text(R.id.product_title, item.title.replace("(Another Widget)", ""))
.text(R.id.product_price, item.price)
.clicked(R.id.item) {
viewModel.purchase(this, item)
}
}
.attachTo(list_view)
viewModel.openConnection()
subscribeUi(viewModel)
action_back.setOnClickListener {
onBackPressed()
}
}
private fun subscribeUi(viewModel: SupportDevViewModel) {
viewModel.products.observe(this, Observer {
if (it.isNotEmpty()) {
loader.isVisible = false
}
adapter.updateData(it.sortedWith(compareBy(SkuDetails::getPriceAmountMicros)))
})
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult.responseCode == OK && purchases != null) {
for (purchase in purchases) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
viewModel.handlePurchase(purchase)
toast(getString(R.string.thanks))
}
}
} else if (billingResult.responseCode == USER_CANCELED) {
// DO nothing
} else {
toast(getString(R.string.error))
}
}
public override fun onDestroy() {
viewModel.closeConnection()
super.onDestroy()
}
}

View File

@ -1,42 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.app.AlertDialog
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.openURI
import kotlinx.android.synthetic.main.activity_weather_provider.*
class WeatherProviderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather_provider)
action_back.setOnClickListener {
onBackPressed()
}
action_save.setOnClickListener {
Preferences.weatherProviderApi = api_key.editText?.text.toString()
setResult(Activity.RESULT_OK)
finish()
}
action_open_provider.setOnClickListener {
openURI("https://home.openweathermap.org/users/sign_up")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
last_info.text = Html.fromHtml(getString(R.string.api_key_info_all_set), Html.FROM_HTML_MODE_LEGACY)
} else {
last_info.text = Html.fromHtml(getString(R.string.api_key_info_all_set))
}
api_key.editText?.setText(Preferences.weatherProviderApi)
}
}

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

@ -0,0 +1,144 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
import android.content.pm.ResolveInfo
import android.os.Bundle
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.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.tabs.AppNotificationsViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
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)
binding = ActivityAppNotificationsFilterBinding.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, ActiveNotificationsHelper.isAppAccepted(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: ActivityAppNotificationsFilterBinding, viewModel: AppNotificationsViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.appList.observe(this, Observer {
updateList(list = it)
binding.loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
binding.clearSearch.isVisible = search.isNotBlank()
})
viewModel.appNotificationsFilter.observe(this, {
updateList()
binding.clearSelection.isVisible = Preferences.appNotificationsFilter != ""
})
}
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 (ActiveNotificationsHelper.isAppAccepted(app1.activityInfo.packageName) && ActiveNotificationsHelper.isAppAccepted(app2.activityInfo.packageName)) {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
} else if (ActiveNotificationsHelper.isAppAccepted(app1.activityInfo.packageName)) {
-1
} else if (ActiveNotificationsHelper.isAppAccepted(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.appNotificationsFilter = ""
}
}
private fun toggleApp(app: ResolveInfo) {
ActiveNotificationsHelper.toggleAppFilter(app.activityInfo.packageName)
}
}

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,184 @@
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
}
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(this)
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

@ -0,0 +1,280 @@
package com.tommasoberlose.anotherwidget.ui.activities.tabs
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.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
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.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.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.tabs.CustomFontViewModel
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.diff.DefaultDiffCallback
class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel
private lateinit var binding: ActivityCustomFontBinding
private lateinit var handlerThread: HandlerThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
binding = ActivityCustomFontBinding.inflate(layoutInflater)
handlerThread = HandlerThread("listCustomFonts")
handlerThread.start()
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter.enableDiff(object: DefaultDiffCallback() {
override fun areItemsTheSame(oldItem: Any?, newItem: Any?): Boolean {
return oldItem is Font && newItem is Font && oldItem.fontFamily == newItem.fontFamily
}
override fun areContentsTheSame(oldItem: Any?, newItem: Any?): Boolean {
return oldItem is Font && newItem is Font && oldItem.fontFamily == newItem.fontFamily
}
})
adapter
.register<String>(R.layout.list_item) { item, injector ->
injector
.text(R.id.text, item)
.with<TextView>(R.id.text) {
val googleSans: Typeface? = androidx.core.content.res.ResourcesCompat.getFont(
this,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
}
)
it.typeface = googleSans
}
injector.clicked(R.id.text) {
val dialog = BottomSheetMenu<String>(this, header = item)
listOf("100", "200", "regular", "500", "700", "800").forEachIndexed { _, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), s)
}
dialog.addOnSelectItemListener { value ->
saveGoogleSansFont(value)
}.show()
}
}
.register<Font>(R.layout.list_item) { item, injector ->
injector
.text(R.id.text, item.fontFamily)
.with<TextView>(R.id.text) {
val request = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
item.queryString,
R.array.com_google_android_gms_fonts_certs
)
class Callback : FontsContractCompat.FontRequestCallback() {
var handler: Handler? = Handler(handlerThread.looper)
fun cancel() {
if (handler != null) {
handler!!.removeCallbacksAndMessages(null)
handler = null
}
}
protected fun finalize() {
cancel()
}
override fun onTypefaceRetrieved(typeface: Typeface) {
if (it.tag == this) {
it.tag = null
it.typeface = typeface
it.setTextColor(getColor(R.color.colorPrimaryText))
}
}
override fun onTypefaceRequestFailed(reason: Int) {
if (it.tag == this) {
it.tag = null
//it.text = item.fontFamily + " ($reason)"
it.setTextColor(getColor(R.color.errorColorText))
}
}
}
(it.tag as Callback?)?.cancel()
val callback = Callback()
it.tag = callback
it.typeface = null
it.setTextColor(getColor(R.color.colorSecondaryText))
val mHandler = callback.handler!!
FontsContractCompat.requestFont(this, request, callback, mHandler)
}
injector.clicked(R.id.text) {
if ((it as TextView).typeface == null) return@clicked
val dialog = BottomSheetMenu<Int>(this, header = item.fontFamily)
if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
} else {
item.fontVariants
.forEachIndexed { index, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), index)
}
}
dialog.addOnSelectItemListener { value ->
saveFont(item, value)
}.show()
}
}
.attachTo(binding.listView)
setupListener()
subscribeUi(binding, viewModel)
binding.search.requestFocus()
setContentView(binding.root)
}
override fun onDestroy() {
handlerThread.quit()
filterJob?.cancel()
super.onDestroy()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityCustomFontBinding, viewModel: CustomFontViewModel) {
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.fontList.observe(this, Observer {
updateList(list = it)
binding.loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
binding.clearSearch.isVisible = search.isNotBlank()
})
}
private fun updateList(
list: ArrayList<Font>? = viewModel.fontList.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<Any> = if (search == null || search == "") {
listOf(getString(R.string.custom_font_subtitle_1)) + list.distinctBy { it.fontFamily }
} else {
(listOf(getString(R.string.custom_font_subtitle_1)) + list.distinctBy { it.fontFamily }).filter {
when (it) {
is Font -> {
it.fontFamily.contains(search, true)
}
is String -> {
it.contains(search, ignoreCase = true)
}
else -> {
true
}
}
}
}.sortedWith { el1, el2 ->
if (el1 is Font && el2 is Font) {
el1.fontFamily.compareTo(el2.fontFamily)
} else if (el1 is Font && el2 is String) {
el1.fontFamily.compareTo(el2)
} else if (el1 is String && el2 is Font) {
el1.compareTo(el2.fontFamily)
} else {
1
}
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
binding.loader.visibility = View.INVISIBLE
}
} else {
delay(200)
withContext(Dispatchers.Main) {
adapter.updateData(listOf(getString(R.string.custom_font_subtitle_1)).filter {
it.contains(search ?: "", ignoreCase = true)
})
}
}
}
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.clearSearch.setOnClickListener {
viewModel.searchInput.value = ""
}
}
private fun saveFont(font: Font, variantPos: Int? = null) {
val resultIntent = Intent()
Preferences.blockingBulk {
customFont = Constants.CUSTOM_FONT_DOWNLOADED
customFontName = font.fontFamily
customFontFile = if (variantPos != null && variantPos > -1) font.getQueryString(variantPos) else font.queryString
customFontVariant = if (variantPos != null && variantPos > -1) font.fontVariants[variantPos] else "regular"
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
private fun saveGoogleSansFont(variant: String) {
val resultIntent = Intent()
Preferences.blockingBulk {
customFont = Constants.CUSTOM_FONT_GOOGLE_SANS
customFontName = ""
customFontFile = ""
customFontVariant = variant
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}

View File

@ -0,0 +1,132 @@
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))
injector.clicked(R.id.text) {
Preferences.bulk {
remove(Preferences::customLocationLat)
remove(Preferences::customLocationLon)
remove(Preferences::customLocationAdd)
}
setResult(Activity.RESULT_OK)
finish()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
injector.text(R.id.text, item.getAddressLine(0) ?: "")
injector.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 setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()
}
binding.clearSearch.setOnClickListener {
viewModel.locationInput.value = ""
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,166 @@
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
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}
.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
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}
.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) {
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}.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()
)
setupListener()
subscribeUi(viewModel)
setContentView(binding.root)
}
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
binding.listView.postDelayed({ updateListItem() }, 300)
}
viewModel.weatherProviderLocationError.observe(this) {
binding.listView.postDelayed({ updateListItem() }, 300)
}
}
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(@Suppress("UNUSED_PARAMETER") ignore: MainFragment.UpdateUiMessageEvent?) {
binding.loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(binding.listView, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()
}
}
}

View File

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

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