Compare commits

...

127 Commits

Author SHA1 Message Date
2762f12151 Add the background location disclaimer 2020-10-02 19:46:52 +02:00
a873c71918 Update the ui 2020-10-02 18:02:22 +02:00
e8f3c110a8 Add the dark theme colors 2020-10-02 17:36:39 +02:00
72bf3b04a7 Fix #156 2020-10-02 16:16:32 +02:00
ca6fb75dfd Fix #191 2020-10-02 15:45:59 +02:00
59dc5de21a Fix #169 2020-10-02 15:00:37 +02:00
ec40a277d7 Fix #186, fix #187 2020-10-02 11:30:59 +02:00
507b7f2318 Fix #174, fix #185 2020-10-02 11:23:23 +02:00
1a709a9406 Merge pull request #172 from Matromag/patch-2
Update translation
2020-10-02 11:19:14 +02:00
770ba0cd13 Merge pull request #173 from Steve-Mr/master
Simplified Chinese translations
2020-10-02 11:18:51 +02:00
6a92225edf Merge pull request #178 from shtraus12/master
fixed Russian localisation
2020-10-02 11:17:46 +02:00
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
25da807bd5 Translated strings to Spanish (master branch) 2020-09-13 20:14:05 -03:00
63212d13b1 translated a few strings to Spanish (master branch) 2020-09-12 21:19:11 -03:00
79ad9b2500 updated file name lol 2020-09-11 22:26:25 -03:00
9c0aa0a9ef added model 2020-09-11 22:25:28 -03:00
b9197ddf2e fix Russian localisation 2020-06-30 15:17:50 +03:00
d1362f7d81 modify chinese translation 2020-06-16 13:17:11 +08:00
a8c7d115ae change one sentence 2020-06-13 16:16:40 +08:00
a5a753e198 Add Chinese translation 2020-06-13 16:14:42 +08:00
39ede20518 Update translation 2020-06-12 15:17:28 +02:00
5527e6e405 Merge pull request #161 from Drumber/translation
Update German translation
2020-05-25 10:59:41 +02:00
565ed11f53 Merge pull request #160 from Moutony/patch-11
Update French (v2.0.12)
2020-05-25 10:30:44 +02:00
4bd8653d32 Merge pull request #154 from zmni/translation
Update Indonesian translation
2020-05-25 10:30:32 +02:00
d121119ca9 Accept and close #162 2020-05-25 10:30:04 +02:00
e82a34bcc7 Fix #157, fix #163 2020-05-25 10:16:21 +02:00
8867a1fbbd Update strings.xml
fixed just another too long string
2020-05-23 13:57:21 +02:00
7265883d6c Update strings.xml
- fixed too long words
- small cosmetic changes
2020-05-23 13:39:28 +02:00
7a4fc6ff58 Update German translation
- removed untranslatable strings
- added new string (line 203: nothing)
- fixed typos
2020-05-23 12:14:17 +02:00
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
7ccf7eb8f6 Update Indonesian translation
Ref: 0d2287dbdf (diff-01dafb3fd0217441330103cfc8300094)
2020-05-23 04:32:15 +07:00
704448a848 Double-tap on the capitalize icon to set the date to uppercase 2020-05-22 19:25:34 +02:00
fa366b3d45 Merge pull request #151 from shtraus12/master
Fixed Russian translation
2020-05-22 19:22:16 +02:00
e58fef66aa Close #148 2020-05-22 19:18:29 +02:00
94825808f4 Update the UI 2020-05-22 18:51:42 +02:00
c120bad9c6 Update strings.xml 2020-05-22 19:48:39 +03:00
0d2287dbdf Fix #144 2020-05-22 18:30:38 +02:00
9e40586456 Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-05-22 18:20:29 +02:00
4d75f4ca0c Fix #150 2020-05-22 18:20:08 +02:00
0859632803 Merge pull request #147 from Drumber/master
Added German translation (values-de)
2020-05-22 17:14:24 +02:00
47562b35ca Merge pull request #146 from zmni/translation
Update Indonesian translation
2020-05-22 17:14:19 +02:00
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
31cf950eee Update ui 2020-05-22 17:13:06 +02:00
f230d300ee fixed minor spelling mistakes 2020-05-21 18:10:55 +02:00
c610857056 Added German translation (values-de) 2020-05-21 17:26:01 +02:00
770040ad93 Fix #136, fix #142 2020-05-21 13:30:15 +02:00
f784817296 Update Indonesian translation 2020-05-21 05:03:57 +07:00
ec1c25cb4c Bugfixes 2020-05-20 20:53:22 +02:00
e1d2f5a782 Update the UI 2020-05-20 20:46:21 +02:00
6e8c6cf055 Update strings.xml 2020-05-20 20:04:11 +02:00
68b5997e8d Update strings.xml 2020-05-20 19:47:53 +02:00
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
fdc02b2cef Update strings.xml
I translated the Glance explanations.
2020-05-20 18:06:40 +02:00
863b8f79d8 Bump the version 2020-05-20 13:46:27 +02:00
857a8009b6 Fix #137 2020-05-20 13:37:37 +02:00
9199f28ad9 Fix french translation and add a new icon pack 2020-05-20 13:34:32 +02:00
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
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
90f0d7de00 Build v2.0.10 2020-05-19 20:07:08 +02:00
66ad5e0839 Fix #133 2020-05-19 20:01:11 +02:00
2ac7e072e5 Close #134 2020-05-19 19:49:21 +02:00
a3392dabcf Add a new cool icon pack 2020-05-19 19:48:33 +02:00
c2c54a04d2 Merge pull request #131 from LastInLine/patch-1
Update strings.xml
2020-05-19 10:28:33 +02:00
2c237058b3 Close #132 2020-05-19 10:27:54 +02:00
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
4c27d1dd9b Fix future events 2020-05-18 20:36:59 +02:00
eba5575ee2 Fix event not found error 2020-05-18 14:14:32 +02:00
aff7e407ca Merge pull request #127 from chreddy/patch-2
Adding Danish translation
2020-05-18 12:47:39 +02:00
f7fc31d968 Fix all day events widget date 2020-05-18 12:44:46 +02:00
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
5d9bd11abb Fix #129 2020-05-18 12:19:15 +02:00
01005ec443 Clean the project 2020-05-18 12:07:04 +02:00
eaf6400e8b Merge pull request #84 from zmni/translation
Create Indonesian translation
2020-05-18 12:05:21 +02:00
30339c7375 Merge pull request #124 from shtraus12/master
Added Russian translation (values-ru)
2020-05-18 12:00:08 +02:00
87da284be4 Update strings 2020-05-18 11:49:57 +02:00
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
79e87b0648 Add files via upload 2020-05-17 16:51:41 +03:00
9e9a91690e Update Indonesian translation 2020-05-17 19:12:38 +07:00
5f699af509 Show translated in-app purchases 2020-05-17 13:25:19 +02:00
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
cebd679856 Update strings 2020-05-17 13:19:55 +02:00
06443ddddb Add settings header 2020-05-16 20:24:11 +02:00
233761a169 Add battery level handler 2020-05-16 14:34:04 +02:00
b81461f725 Fix #120 2020-05-16 12:47:29 +02:00
26b1948b70 Update Indonesian translation 2020-05-14 22:49:58 +07:00
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
b04b103634 Update the build version 2020-05-13 19:04:17 +02:00
1dc050e77f Update event repository 2020-05-13 18:58:45 +02:00
ff171d4022 Update the events selector. Fix #95 2020-05-13 18:57:44 +02:00
d91471d1ee Fix #113, #111, #79, #107, #109 2020-05-13 17:42:48 +02:00
ac381c8542 Add weather icon pack, updates frequency and fix google fit 2020-05-12 23:54:07 +02:00
ba5a860e75 Update ui 2020-05-11 13:33:37 +02:00
09bc9df22f Update icons 2020-05-11 13:30:36 +02:00
385806413e Fix dark theme toggle issue 2020-05-11 12:40:42 +02:00
7d2ea5a4d8 Bugfixes 2020-05-11 00:38:32 +02:00
ce9f5a6e45 Update google fit integration 2020-05-11 00:00:31 +02:00
8f0f6bc868 Update beta release 2020-05-10 20:42:48 +02:00
34d8fa4b59 update gitignore 2020-05-10 20:32:48 +02:00
9401b89036 Clean project files 2020-05-10 20:31:50 +02:00
842c0d764f Add google fit connection 2020-05-10 20:29:35 +02:00
1754b4045b Add google fit integration 2020-05-10 14:08:40 +02:00
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
7f41a92a91 Update Indonesian translation 2020-05-10 04:06:26 +07:00
9a63b9bde2 Gix switcher, update glance, add glance order, add ampm toggle 2020-05-09 22:37:59 +02:00
26428b65da Merge crud-device 2020-05-09 17:58:03 +02:00
97d1caeabc Add dividers toggle 2020-05-09 15:11:12 +02:00
f013be5a7a Add integreations activity, removed long time until intervals 2020-05-08 20:16:48 +02:00
4cc55edb15 Merge music 2020-05-08 18:57:14 +02:00
d08ad6171e Change music fragment to at a glance 2020-05-08 18:55:08 +02:00
9e2eacef73 Update build version 2020-05-08 17:43:47 +02:00
f8f8a8f051 Merge calendar fix 2020-05-08 15:15:49 +02:00
60531061d7 Fix #83 2020-05-08 15:14:01 +02:00
654ec3fe66 Add current song 2020-05-08 15:10:19 +02:00
40c9253159 Create Indonesian translation 2020-05-08 19:04:48 +07:00
d86d2cadd4 Add clock text color 2020-05-08 11:55:42 +02:00
cb6c2c7764 Move app settings to a different fragment 2020-05-08 01:39:31 +02:00
6a4cfdf22b Update bitmap generator 2020-05-07 18:30:28 +02:00
0b9e9e081e Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-05-07 16:19:53 +02:00
15884bd9b2 Fix helper 2020-05-07 16:19:30 +02:00
0adf192965 Fix #72 2020-05-07 15:51:09 +02:00
d72ba08960 Update issue templates 2020-05-07 14:14:26 +02:00
1848951a74 Update issue templates 2020-05-07 14:13:14 +02:00
5ca06e817e update wallpaper ui, update color picker ui, update intents and add custom logger for the widget 2020-05-07 12:34:48 +02:00
37cb43dc80 Add media player receiver 2020-05-06 21:40:51 +02:00
ea372dd76d Remove workers. Fix #71 2020-05-06 21:25:23 +02:00
7076311e94 Add workers 2020-05-06 20:58:32 +02:00
2e684e0902 Fix #74, fix #69 2020-05-06 16:16:31 +02:00
1304 changed files with 9468 additions and 3160 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: tommasoberlose
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Smartphone (please complete the following information):**
- Device: [e.g. OnePlus 6]
- OS Version: [e.g. Android 9.0]
- App Version (that you can find inside the advanced settings) [e.g. v2.0.5 (71)]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: tommasoberlose
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

6
.gitignore vendored
View File

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

Binary file not shown.

View File

@ -1,6 +1,22 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<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" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

1
.idea/gradle.xml generated
View File

@ -11,7 +11,6 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/tasksintegration" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

3
.idea/modules.xml generated
View File

@ -2,9 +2,8 @@
<project version="4">
<component name="ProjectModuleManager">
<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" group="Another_Widget" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="Another Widget/app" />
<module fileurl="file://$PROJECT_DIR$/tasksintegration/tasksintegration.iml" filepath="$PROJECT_DIR$/tasksintegration/tasksintegration.iml" group="Another Widget/tasksintegration" />
</modules>
</component>
</project>

52
.idea/navEditor.xml generated Normal file
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="nav_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="appMainFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="242" />
<option name="y" value="352" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_appMainFragment_to_appSettingsFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="appSettingsFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="532" />
<option name="y" value="353" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -13,6 +13,13 @@ Also, as much as possible, there are always updates and new features in the shor
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>
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
-------
Copyright (C) 2017-2020 Tommaso Berlose (http://tommasoberlose.com)

View File

@ -18,8 +18,8 @@ android {
applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23
targetSdkVersion 29
versionCode 69
versionName "2.0.5"
versionCode 105
versionName "2.0.13"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -52,8 +52,6 @@ android {
}
viewBinding.enabled = true
dynamicFeatures = [":tasksintegration"]
}
dependencies {
@ -61,13 +59,13 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.2.0-alpha06'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'androidx.browser:browser:1.2.0'
implementation 'net.idik:slimadapter:2.1.2'
implementation 'com.google.android:flexbox:2.0.1'
@ -77,18 +75,18 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx: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.4.0"
// EventBus
implementation 'org.greenrobot:eventbus:3.1.1'
// Navigation
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha05'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
// Other
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'joda-time:joda-time:2.9.9'
implementation 'joda-time:joda-time:2.10.3'
implementation 'me.everything:providers-android:1.0.1'
implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.2'
@ -96,35 +94,32 @@ dependencies {
implementation 'com.github.bumptech.glide:glide: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.1.0'
//Weather
implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.gms:play-services-location:17.1.0'
// Billing
implementation 'com.android.billingclient:billing:2.2.0'
implementation 'com.android.billingclient:billing-ktx:2.2.0'
implementation 'com.android.billingclient:billing:3.0.1'
implementation 'com.android.billingclient:billing-ktx:3.0.1'
// KTX
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.2.0'
// Recommended: Add the Firebase SDK for Google Analytics.
implementation 'com.google.firebase:firebase-analytics:17.4.0'
implementation 'androidx.core:core-ktx:1.3.2'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.0.0'
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
// Preferences
implementation 'com.chibatching.kotpref:kotpref:2.10.0'
implementation 'com.chibatching.kotpref:kotpref:2.11.0'
implementation 'com.chibatching.kotpref:livedata-support:2.10.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
// Permissions
implementation 'com.karumi:dexter:6.1.0'
// Billing
implementation 'com.android.billingclient:billing:2.2.0'
implementation 'com.android.billingclient:billing-ktx:2.2.0'
}

View File

@ -1,40 +0,0 @@
{
"project_info": {
"project_number": "791844924473",
"firebase_url": "https://anotherwidget-182008.firebaseio.com",
"project_id": "anotherwidget-182008",
"storage_bucket": "anotherwidget-182008.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:791844924473:android:0ad4f6e3890f1ad320b1e8",
"android_client_info": {
"package_name": "com.tommasoberlose.anotherwidget"
}
},
"oauth_client": [
{
"client_id": "791844924473-73dh46rorjq8vm97dgbn6can2dcpqlf0.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAeJRXstqnzebibxmm3FRM98nbwE_kC8tA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "791844924473-73dh46rorjq8vm97dgbn6can2dcpqlf0.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

Binary file not shown.

View File

@ -10,6 +10,10 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<application
android:allowBackup="true"
@ -32,6 +36,7 @@
<activity android:name=".ui.activities.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">
@ -80,10 +85,10 @@
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<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.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
@ -99,8 +104,52 @@
</intent-filter>
</receiver>
<receiver
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=".services.BatteryListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".receivers.MusicNotificationListener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<receiver android:name=".receivers.BatteryLevelReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
<action android:name="android.intent.action.BATTERY_LOW"/>
<action android:name="android.intent.action.BATTERY_OKAY"/>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name=".receivers.ActivityDetectionReceiver"
android:exported="false"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
<intent-filter>
<action android:name="com.mypackage.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".services.UpdateCalendarJob"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application>
</manifest>

View File

@ -1,6 +1,7 @@
package com.tommasoberlose.anotherwidget
import android.app.Application
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import com.chibatching.kotpref.Kotpref
import com.google.firebase.crashlytics.FirebaseCrashlytics

View File

@ -8,18 +8,24 @@ import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.SeekBar
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.reveal
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
@ -28,13 +34,14 @@ import kotlinx.android.synthetic.main.bottom_sheet_menu_hor.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu_hor.view.color_loader
import kotlinx.android.synthetic.main.color_picker_menu_item.view.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.util.prefs.Preferences
class BottomSheetColorPicker(
context: Context,
private val colors: IntArray = intArrayOf(),
private val selected: Int? = null,
private val getSelected: (() -> Int)? = null,
private val header: String? = null,
private val onColorSelected: ((selectedValue: Int) -> Unit)? = null,
private val showAlphaSelector: Boolean = false,
@ -42,15 +49,19 @@ class BottomSheetColorPicker(
private val onAlphaChangeListener: ((alpha: Int) -> Unit)? = null
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private val loadingJob: Job? = null
private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter
override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_menu_hor, null)
window?.setDimAmount(0f)
// Header
view.header.isVisible = header != null
view.header_text.text = header ?: ""
// Alpha
view.alpha_selector_container.isVisible = showAlphaSelector
view.alpha_selector.setProgress(alpha.toFloat())
view.text_alpha.text = "%s: %s%%".format(context.getString(R.string.alpha), alpha)
@ -67,44 +78,62 @@ class BottomSheetColorPicker(
}
}
val itemViews: ArrayList<View> = ArrayList()
// List
GlobalScope.launch(Dispatchers.IO) {
for (@ColorInt color: Int in colors) {
val itemView = View.inflate(context, R.layout.color_picker_menu_item, null)
itemView.color.setCardBackgroundColor(ColorStateList.valueOf(color))
itemView.check.setColorFilter(ContextCompat.getColor(context,
if (color.isColorDark()) android.R.color.white else android.R.color.black
), android.graphics.PorterDuff.Mode.MULTIPLY)
itemView.check.isVisible = selected == color
itemView.color.setOnClickListener {
onColorSelected?.invoke(color)
this@BottomSheetColorPicker.dismiss()
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
val listView = View.inflate(context, R.layout.bottom_sheet_menu_list, null) as RecyclerView
listView.setHasFixedSize(true)
val mLayoutManager = GridLayoutManager(context, 6)
listView.layoutManager = mLayoutManager
adapter
.register<Int>(R.layout.color_picker_menu_item) { item, injector ->
injector
.with<MaterialCardView>(R.id.color) {
it.setCardBackgroundColor(ColorStateList.valueOf(item))
}
.with<AppCompatImageView>(R.id.check) {
if (getSelected?.invoke() == item) {
it.setColorFilter(
ContextCompat.getColor(
context,
if (item.isColorDark()) android.R.color.white else android.R.color.black
),
android.graphics.PorterDuff.Mode.MULTIPLY
)
it.isVisible = true
} else {
it.isVisible = false
}
}
.clicked(R.id.color) {
adapter.notifyItemChanged(adapter.data.indexOf(getSelected?.invoke()))
onColorSelected?.invoke(item)
val position = adapter.data.indexOf(item)
adapter.notifyItemChanged(position)
(listView.layoutManager as GridLayoutManager).scrollToPositionWithOffset(position,0)
}
}
itemViews.add(itemView)
}
.attachTo(listView)
adapter.updateData(colors.toList())
withContext(Dispatchers.Main) {
itemViews.forEach {
view.menu.addView(it, GridLayout.LayoutParams(
GridLayout.spec(GridLayout.UNDEFINED, 1f),
GridLayout.spec(GridLayout.UNDEFINED, 1f)
))
}
color_loader.isVisible = false
view.menu.isVisible = true
view.color_loader.isVisible = false
view.list_container.addView(listView)
this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
view.list_container.isVisible = true
}
}
// Menu
})
setContentView(view)
super.show()
}
override fun onStop() {
loadingJob?.cancel()
loadingJobs.forEach { it.cancel() }
super.onStop()
}

View File

@ -1,17 +1,11 @@
package com.tommasoberlose.anotherwidget.components
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.annotation.MenuRes
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu_item.view.*
@ -21,7 +15,7 @@ import kotlinx.android.synthetic.main.bottom_sheet_menu_item.view.*
* theme which sets a rounded background to the dialog
* and doesn't dim the navigation bar
*/
open class BottomSheetMenu<T>(context: Context, private val header: String? = null, private val isMultiSelection: Boolean = false) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
open class BottomSheetMenu<T>(context: Context, private val header: String? = null, private val message: String? = null, private val isMessageWarning: Boolean = false, private val isMultiSelection: Boolean = false) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private val items: ArrayList<MenuItem<T>> = ArrayList()
private var selectedRes: ArrayList<T> = ArrayList()
@ -60,6 +54,10 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
view.header.isVisible = header != null
view.header_text.text = header ?: ""
view.warning_text.isVisible = message != null
view.warning_text.text = message ?: ""
view.warning_text.setTextColor(ContextCompat.getColor(context, if (isMessageWarning) R.color.warningColorText else R.color.colorSecondaryText))
// Menu
for (item in items) {
if (item.value != null) {

View File

@ -0,0 +1,27 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.view.View
import androidx.appcompat.app.AlertDialog
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import kotlinx.android.synthetic.main.custom_notes_dialog_layout.view.*
class CustomNotesDialog(context: Context) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
init {
val view = View.inflate(context, R.layout.custom_notes_dialog_layout, null)
view.notes.setText(Preferences.customNotes)
view.action_positive.setOnClickListener {
Preferences.customNotes = view.notes.text.toString()
this.dismiss()
}
view.notes.requestFocus()
setContentView(view)
}
}

View File

@ -0,0 +1,24 @@
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
class FixedFocusScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : ScrollView(context, attrs, defStyle) {
var isScrollable = true
override fun scrollTo(x: Int, y: Int) {
if (isScrollable) {
super.scrollTo(x, y)
}
}
}

View File

@ -0,0 +1,98 @@
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(context)
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(context)
.mapNotNull { GlanceProviderHelper.getGlanceProviderById(context, it) }
)
setContentView(view)
super.show()
}
}

View File

@ -0,0 +1,57 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.view.View
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.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.header
import kotlinx.android.synthetic.main.fragment_weather_settings.*
import kotlinx.android.synthetic.main.icon_pack_menu_item.view.*
class IconPackSelector(context: Context, private val header: String? = null) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
override fun show() {
val view = View.inflate(context, R.layout.bottom_sheet_menu, null)
// Header
view.header.isVisible = header != null
view.header_text.text = header ?: ""
view.warning_text.isVisible = false
// Menu
for (item in Constants.WeatherIconPack.values()) {
val itemView = View.inflate(context, R.layout.icon_pack_menu_item, null)
itemView.label.text = context.getString(R.string.settings_weather_icon_pack_default).format(item.value + 1)
itemView.isSelected = item.value == Preferences.weatherIconPack
itemView.icon_1.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("01d", item.value)))
itemView.icon_2.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("01n", item.value)))
itemView.icon_3.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("10d", item.value)))
itemView.icon_4.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("09n", item.value)))
listOf<ImageView>(itemView.icon_1, itemView.icon_2, itemView.icon_3, itemView.icon_4).forEach {
if (item == Constants.WeatherIconPack.MINIMAL) {
it.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimaryText))
} else {
it.setColorFilter(ContextCompat.getColor(context, android.R.color.transparent))
}
}
itemView.setOnClickListener {
Preferences.weatherIconPack = item.value
this.dismiss()
}
view.menu.addView(itemView)
}
setContentView(view)
super.show()
}
}

View File

@ -11,8 +11,8 @@ typealias DialogCallback = () -> Unit
class MaterialBottomSheetDialog(
context: Context,
private val title: String? = "",
private val message: String? = ""
private val title: String? = null,
private val message: String? = null
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var positiveButtonLabel: String? = null
@ -36,7 +36,7 @@ class MaterialBottomSheetDialog(
val view = View.inflate(context, R.layout.bottom_sheet_dialog, null)
// Header
view.message.isVisible = title != null
view.title.isVisible = title != null
view.title.text = title ?: ""
view.message.isVisible = message != null

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

@ -1,29 +1,37 @@
package com.tommasoberlose.anotherwidget.db
import android.content.Context
import android.provider.CalendarContract
import android.util.Log
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import io.realm.Realm
import io.realm.RealmResults
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
class EventRepository(val context: Context) {
private val realm by lazy { Realm.getDefaultInstance() }
fun saveEvents(eventList: ArrayList<Event>) {
realm.executeTransactionAsync { realm ->
realm.executeTransaction { realm ->
realm.where(Event::class.java).findAll().deleteAllFromRealm()
realm.copyToRealm(eventList)
}
}
fun resetNextEventData() {
realm.executeTransactionAsync {
it.where(Event::class.java).findAll().deleteAllFromRealm()
fun clearEvents() {
realm.executeTransaction { realm ->
realm.where(Event::class.java).findAll().deleteAllFromRealm()
}
}
fun resetNextEventData() {
Preferences.bulk {
remove(Preferences::nextEventId)
remove(Preferences::nextEventName)
@ -36,22 +44,63 @@ class EventRepository(val context: Context) {
}
fun saveNextEventData(event: Event) {
Preferences.nextEventId = event.id
Preferences.nextEventId = event.eventID
}
fun getNextEvent(): Event? = realm.where(Event::class.java).equalTo("id", Preferences.nextEventId).findFirst() ?: realm.where(Event::class.java).findFirst()
fun getNextEvent(): Event? {
val nextEvent = getEventByEventId(Preferences.nextEventId)
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)
}
}
val event = if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate < limit.timeInMillis) {
nextEvent
} else {
val events = getEvents()
if (events.isNotEmpty()) {
val newNextEvent = events.first()
Preferences.nextEventId = newNextEvent.eventID
newNextEvent
} else {
resetNextEventData()
null
}
}
return try {
realm.copyFromRealm(event!!)
} catch (ex: Exception) {
event
}
}
fun getEventById(id: Long): Event? = realm.where(Event::class.java).equalTo("id", id).findFirst()
fun getEventByEventId(id: Long): Event? {
val event = realm.where(Event::class.java).equalTo("eventID", id).findFirst()
return try {
realm.copyFromRealm(event!!)
} catch (ex: Exception) {
event
}
}
fun goToNextEvent() {
val eventList = realm.where(Event::class.java).findAll()
val eventList = getEvents()
if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId }
if (index > -1 && index < eventList.size - 1) {
Preferences.nextEventId = eventList[index + 1]!!.id
Preferences.nextEventId = eventList[index + 1].eventID
} else {
Preferences.nextEventId = eventList.first()!!.id
Preferences.nextEventId = eventList.first().eventID
}
} else {
resetNextEventData()
@ -61,14 +110,13 @@ class EventRepository(val context: Context) {
}
fun goToPreviousEvent() {
val eventList = realm.where(Event::class.java).findAll()
val eventList = getEvents()
if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId }
if (index > 0) {
Preferences.nextEventId = eventList[index - 1]!!.id
Preferences.nextEventId = eventList[index - 1].eventID
} else {
Preferences.nextEventId = eventList.last()!!.id
Preferences.nextEventId = eventList.last().eventID
}
} else {
resetNextEventData()
@ -77,7 +125,44 @@ class EventRepository(val context: Context) {
MainWidget.updateWidget(context)
}
fun getEvents(): RealmResults<Event> = realm.where(Event::class.java).findAll()
fun getFutureEvents(): List<Event> {
val now = Calendar.getInstance().timeInMillis
realm.refresh()
return realm
.where(Event::class.java)
.greaterThan("endDate", now)
.findAll()
.applyFilters()
}
fun getEventsCount(): Int = realm.where(Event::class.java).findAll().size
private fun getEvents(): List<Event> {
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)
}
}
realm.refresh()
return realm
.where(Event::class.java)
.greaterThan("endDate", now)
.lessThanOrEqualTo("startDate", limit.timeInMillis)
.findAll()
.applyFilters()
}
fun getEventsCount(): Int = getEvents().size
fun close() {
realm.close()
}
}

View File

@ -4,10 +4,11 @@ object Actions {
const val ACTION_EXTRA_OPEN_WEATHER_PROVIDER = "ACTION_EXTRA_OPEN_WEATHER_PROVIDER"
const val ACTION_EXTRA_DISABLE_GPS_NOTIFICATION = "ACTION_EXTRA_DISABLE_GPS_NOTIFICATION"
const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.ACTION_TIME_UPDATE"
const val ACTION_CALENDAR_UPDATE = "com.tommasoberlose.anotherwidget.action.ACTION_CALENDAR_UPDATE"
const val ACTION_WEATHER_UPDATE = "com.tommasoberlose.anotherwidget.action.ACTION_WEATHER_UPDATE"
const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.ACTION_OPEN_WEATHER_INTENT"
const val ACTION_TIME_UPDATE = "com.tommasoberlose.anotherwidget.action.TIME_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_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_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT"
const val ACTION_REPORT_CRASH = "com.tommasoberlose.anotherwidget.action.REPORT_CRASH"
}

View File

@ -14,4 +14,32 @@ object Constants {
MEDIUM(2),
LARGE(3)
}
enum class SecondRowTopMargin(val value: Int) {
NONE(0),
SMALL(1),
MEDIUM(2),
LARGE(3)
}
enum class GlanceProviderId(val id: String) {
PLAYING_SONG("PLAYING_SONG"),
NEXT_CLOCK_ALARM("NEXT_CLOCK_ALARM"),
BATTERY_LEVEL_LOW("BATTERY_LEVEL_LOW"),
CUSTOM_INFO("CUSTOM_INFO"),
GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS")
}
enum class WidgetUpdateFrequency(val value: Int) {
LOW(0),
DEFAULT(1),
HIGH(2)
}
enum class WeatherIconPack(val value: Int) {
DEFAULT(0),
MINIMAL(1),
COOL(2),
GOOGLE_NEWS(3)
}
}

View File

@ -7,8 +7,9 @@ import com.chibatching.kotpref.KotprefModel
object Preferences : KotprefModel() {
override val commitAllPropertiesByDefault: Boolean = true
var darkThemePreference by intPref(default = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) MODE_NIGHT_FOLLOW_SYSTEM else MODE_NIGHT_AUTO_BATTERY)
var darkThemePreference by intPref(default = MODE_NIGHT_FOLLOW_SYSTEM)
// Calendar and weather
var showEvents by booleanPref(key = "PREF_SHOW_EVENTS", default = false)
var showWeather by booleanPref(key = "PREF_SHOW_WEATHER", default = false)
var weatherIcon by stringPref(key = "PREF_WEATHER_ICON", default = "")
@ -29,6 +30,8 @@ object Preferences : KotprefModel() {
var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "")
var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", 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 showUntil by intPref(key = "PREF_SHOW_UNTIL", default = 1)
var calendarAppName by stringPref(key = "PREF_CALENDAR_APP_NAME", default = "")
@ -39,31 +42,86 @@ object Preferences : KotprefModel() {
var eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "")
var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "")
var openEventDetails by booleanPref(default = true)
var widgetUpdateFrequency by intPref(default = Constants.WidgetUpdateFrequency.DEFAULT.value)
var textGlobalColor by stringPref(key = "PREF_TEXT_COLOR", default = "#FFFFFF")
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 backgroundCardAlpha by stringPref(default = "00")
var clockTextColor by stringPref(default = "#FFFFFF")
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 weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.value)
// Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 90f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.value)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.value)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")
var showNextAlarm by booleanPref(default = false)
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 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 customFont by intPref(key = "PREF_CUSTOM_FONT", default = Constants.CUSTOM_FONT_PRODUCT_SANS)
var customFontFile by stringPref(key = "PREF_CUSTOM_FONT_FILE")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showDividers by booleanPref(default = true)
// Settings
var showWallpaper by booleanPref(default = true)
var showBigClockWarning by booleanPref(default = true)
var showWeatherWarning by booleanPref(default = true)
var showPreview by booleanPref(default = true)
var showXiaomiWarning by booleanPref(default = true)
// Glance
var showGlance by booleanPref(default = true)
var enabledGlanceProviderOrder by stringPref(default = "")
var customNotes by stringPref(default = "")
var showNextAlarm by booleanPref(default = true)
var showBatteryCharging by booleanPref(default = false)
var isBatteryLevelLow by booleanPref(default = false)
var isCharging by booleanPref(default = false)
var googleFitSteps by longPref(default = -1)
var showDailySteps by booleanPref(default = false)
var showMusic by booleanPref(default = false)
var mediaInfoFormat by stringPref(default = "")
var mediaPlayerTitle by stringPref(default = "")
var mediaPlayerAlbum by stringPref(default = "")
var mediaPlayerArtist by stringPref(default = "")
var mediaPlayerPackage by stringPref(default = "")
// Integrations
var installedIntegrations by intPref(default = 0)
}

View File

@ -12,8 +12,7 @@ object AlarmHelper {
val alarm = nextAlarmClock
return if (
alarm != null
&& alarm.triggerTime - Calendar.getInstance().timeInMillis > 10 * 60 * 1000
&& alarm.triggerTime - Calendar.getInstance().timeInMillis < 12 * 60 * 60 * 1000
&& alarm.triggerTime - Calendar.getInstance().timeInMillis > 5 * 60 * 1000
) {
"%s %s".format(
SimpleDateFormat("EEE", Locale.getDefault()).format(alarm.triggerTime),
@ -23,4 +22,14 @@ object AlarmHelper {
""
}
}
fun isAlarmProbablyWrong(context: Context): Boolean {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
return (
alarm != null
&& alarm.triggerTime - Calendar.getInstance().timeInMillis < 5 * 60 * 1000
)
}
}
}

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

@ -15,20 +15,46 @@ object BitmapHelper {
fun getBitmapFromView(view: View, width: Int? = null, height: Int? = null, draw: Boolean = true): Bitmap {
//Define a bitmap with the same size as the view
val measuredWidth = View.MeasureSpec.makeMeasureSpec(width ?: view.width, if (width != null) View.MeasureSpec.EXACTLY else View.MeasureSpec.UNSPECIFIED)
val measuredWidth = View.MeasureSpec.makeMeasureSpec(width ?: view.width, if (width != null) View.MeasureSpec.EXACTLY else View.MeasureSpec.AT_MOST)
val measuredHeight = View.MeasureSpec.makeMeasureSpec(height ?: view.height, if (height != null) View.MeasureSpec.EXACTLY else View.MeasureSpec.UNSPECIFIED)
view.measure(measuredWidth, measuredHeight)
view.measure(
if (measuredWidth > 0) measuredWidth else 0,
if (measuredHeight > 0) measuredHeight else 0
)
val calculatedWidth = view.measuredWidth
val widgetWidth = if (calculatedWidth in 1..16000) {
calculatedWidth
} else if (width != null && width > 0) {
width
} else {
1
}
val calculatedHeight = view.measuredHeight
val widgetHeight = if (calculatedHeight in 1..16000) {
calculatedHeight
} else if (height != null && height > 0) {
height
} else {
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 {
if (draw) {
Log.d("ciao", "bitmap ${view.measuredWidth}, ${view.measuredHeight}")
}
val btm = Bitmap.createBitmap(
view.measuredWidth,
view.measuredHeight,
if (true || draw) Bitmap.Config.ARGB_8888 else Bitmap.Config.ALPHA_8
widgetWidth,
widgetHeight,
if (draw) Bitmap.Config.ARGB_8888 else Bitmap.Config.ALPHA_8
)
if (true || draw) {
if (draw) {
//Bind a canvas to it
val canvas = Canvas(btm)
// draw the view on the canvas
@ -78,13 +104,12 @@ object BitmapHelper {
}
fun drawableToBitmap(drawable: Drawable): Bitmap? {
var bitmap: Bitmap? = null
if (drawable is BitmapDrawable) {
if (drawable.bitmap != null) {
return drawable.bitmap
}
}
bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) {
val bitmap: Bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) {
Bitmap.createBitmap(
1,
1,

View File

@ -1,8 +1,14 @@
package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import android.util.Log
import com.tommasoberlose.anotherwidget.services.EventListenerJob
@ -10,7 +16,8 @@ import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.services.UpdateCalendarJob
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider
@ -24,111 +31,8 @@ import kotlin.collections.ArrayList
*/
object CalendarHelper {
fun updateEventList(context: Context) {
val eventRepository = EventRepository(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)
Log.d("ciao", "force update? 7")
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainActivity.UpdateUiMessageEvent())
UpdateCalendarJob.enqueueWork(context, Intent())
}
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -150,7 +54,8 @@ object CalendarHelper {
}
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>) {
@ -164,4 +69,15 @@ object CalendarHelper {
fun removeEventUpdatesAndroidN(context: Context) {
EventListenerJob.remove(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()
}
}

View File

@ -7,33 +7,97 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import kotlin.math.roundToInt
object ColorHelper {
fun getFontColor(): Int {
fun getFontColor(isDark: Boolean): Int {
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) {
Color.parseColor("#FFFFFFFF")
}
}
fun getBackgroundColor(): Int {
fun getFontColorAlpha(isDark: Boolean): Int {
return try {
Color.parseColor("#%s%s".format(Preferences.backgroundCardAlpha, Preferences.backgroundCardColor.replace("#", "")))
(if (!isDark) Preferences.textGlobalAlpha else Preferences.textGlobalAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) {
"FF".toIntValue().toDouble() * 255 / 100
}.roundToInt()
}
fun getFontColorRgb(isDark: Boolean): Int {
return try {
Color.parseColor((if (!isDark) Preferences.textGlobalColor else Preferences.textGlobalColorDark))
} catch (e: Exception) {
Color.parseColor("#000000")
}
}
fun getSecondaryFontColor(isDark: Boolean): Int {
return try {
Color.parseColor("#%s%s".format((if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark), (if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark).replace("#", "")))
} catch (e: Exception) {
Color.parseColor("#FFFFFFFF")
}
}
fun getSecondaryFontColorAlpha(isDark: Boolean): Int {
return try {
(if (!isDark) Preferences.textSecondaryAlpha else Preferences.textSecondaryAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) {
"FF".toIntValue().toDouble() * 255 / 100
}.roundToInt()
}
fun getSecondaryFontColorRgb(isDark: Boolean): Int {
return try {
Color.parseColor((if (!isDark) Preferences.textSecondaryColor else Preferences.textSecondaryColorDark))
} catch (e: Exception) {
Color.parseColor("#000000")
}
}
fun getClockFontColor(isDark: Boolean): Int {
return try {
Color.parseColor("#%s%s".format((if (!isDark) Preferences.clockTextAlpha else Preferences.clockTextAlphaDark), (if (!isDark) Preferences.clockTextColor else Preferences.clockTextColorDark).replace("#", "")))
} 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) {
Color.parseColor("#00000000")
}
}
fun getBackgroundAlpha(): Int {
fun getBackgroundAlpha(isDark: Boolean): Int {
return try {
Preferences.backgroundCardAlpha.toIntValue().toDouble() * 255 / 100
(if (!isDark) Preferences.backgroundCardAlpha else Preferences.backgroundCardAlphaDark).toIntValue().toDouble() * 255 / 100
} catch (e: Exception) {
"00".toIntValue().toDouble() * 255 / 100
}.roundToInt()
}
fun getBackgroundColorRgb(): Int {
fun getBackgroundColorRgb(isDark: Boolean): Int {
return try {
Color.parseColor(Preferences.backgroundCardColor)
Color.parseColor((if (!isDark) Preferences.backgroundCardColor else Preferences.backgroundCardColorDark))
} catch (e: Exception) {
Color.parseColor("#000000")
}

View File

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

View File

@ -0,0 +1,95 @@
package com.tommasoberlose.anotherwidget.helpers
import android.content.Context
import android.util.Log
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.utils.checkIfFitInstalled
import java.util.ArrayList
object GlanceProviderHelper {
fun getGlanceProviders(context: Context): ArrayList<Constants.GlanceProviderId> {
val enabledProviders = Preferences.enabledGlanceProviderOrder.split(",").filter { it != "" }
val providers = Constants.GlanceProviderId.values()
.filter {
context.checkIfFitInstalled() || it != Constants.GlanceProviderId.GOOGLE_FIT_STEPS
}.toTypedArray()
providers.sortWith(Comparator { p1, p2 ->
when {
enabledProviders.contains(p1.id) && enabledProviders.contains(p2.id) -> {
enabledProviders.indexOf(p1.id).compareTo(enabledProviders.indexOf(p2.id))
}
enabledProviders.contains(p1.id) -> {
-1
}
enabledProviders.contains(p2.id) -> {
1
}
else -> {
p1.id.compareTo(p2.id)
}
}
})
return ArrayList(providers.toList())
}
fun getGlanceProviderById(context: Context, providerId: Constants.GlanceProviderId): GlanceProvider? {
return when(providerId) {
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_next_alarm_title),
R.drawable.round_alarm
)
}
Constants.GlanceProviderId.PLAYING_SONG -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_music_title),
R.drawable.round_music_note
)
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_custom_notes_title),
R.drawable.round_notes
)
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_low_battery_level_title),
R.drawable.round_battery_charging_full
)
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_daily_steps_title),
R.drawable.round_directions_walk
)
}
}
}
fun saveGlanceProviderOrder(list: ArrayList<Constants.GlanceProviderId>) {
Preferences.enabledGlanceProviderOrder = list.joinToString(separator = ",")
}
fun showGlanceProviders(context: Context): Boolean {
val eventRepository = EventRepository(context)
BatteryHelper.updateBatteryInfo(context)
val showGlance = Preferences.showGlance && (eventRepository.getEventsCount() == 0 || !Preferences.showEvents) && (
(Preferences.showNextAlarm && AlarmHelper.getNextAlarm(context) != "") ||
(MediaPlayerHelper.isSomeonePlaying(context)) ||
(Preferences.showBatteryCharging && Preferences.isCharging || Preferences.isBatteryLevelLow) ||
(Preferences.customNotes.isNotEmpty()) ||
(Preferences.showDailySteps && Preferences.googleFitSteps > 0)
)
eventRepository.close()
return showGlance
}
}

View File

@ -1,5 +1,6 @@
package com.tommasoberlose.anotherwidget.helpers
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.ContentUris
import android.content.Context
@ -10,13 +11,27 @@ import android.provider.AlarmClock
import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import android.util.Log
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toast
import java.util.*
object IntentHelper {
fun getGoogleMapsIntentFromAddress(context: Context, address:String): Intent {
fun getWidgetUpdateIntent(context: Context): Intent {
val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java)
val widgetIds = widgetManager.getAppWidgetIds(widgetComponent)
return Intent(context, MainWidget::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds)
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
}
}
fun getGoogleMapsIntentFromAddress(context: Context, address: String): Intent {
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=$address")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.`package` = "com.google.android.apps.maps"
@ -51,23 +66,23 @@ object IntentHelper {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
}
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
}
}
fun getCalendarIntent(context: Context): Intent {
val calendarUri = CalendarContract.CONTENT_URI
.buildUpon()
.appendPath("time")
.appendPath(Calendar.getInstance().timeInMillis.toString())
.build()
return when (Preferences.calendarAppPackage) {
"" -> {
Intent(Intent.ACTION_MAIN).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addCategory(Intent.CATEGORY_APP_CALENDAR)
Intent(Intent.ACTION_VIEW).apply {
data = calendarUri
}
}
"_" -> {
@ -77,15 +92,12 @@ object IntentHelper {
val pm: PackageManager = context.packageManager
try {
pm.getLaunchIntentForPackage(Preferences.calendarAppPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_VIEW
data = calendarUri
}
} catch (e: Exception) {
e.printStackTrace()
Intent(Intent.ACTION_MAIN).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addCategory(Intent.CATEGORY_APP_CALENDAR)
}
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
}
@ -98,16 +110,51 @@ object IntentHelper {
if (Preferences.calendarAppPackage == "") {
Intent(Intent.ACTION_VIEW).apply {
data = uri
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate)
flags = (Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP)
if (!e.allDay) {
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
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 {
getCalendarIntent(context).apply {
action = Intent.ACTION_VIEW
data = uri
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, e.endDate)
val calendarIntent = getCalendarIntent(context)
if (calendarIntent.action == Intent.ACTION_VIEW) {
calendarIntent.apply {
data = uri
if (!e.allDay) {
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, e.startDate)
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()
}
}
}
@ -134,11 +181,45 @@ object IntentHelper {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
}
}
fun getBatteryIntent(): Intent {
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY)
}
fun getMusicIntent(context: Context): Intent {
return when (Preferences.mediaPlayerPackage) {
"" -> {
Intent()
}
else -> {
val pm: PackageManager = context.packageManager
try {
pm.getLaunchIntentForPackage(Preferences.mediaPlayerPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
}
}
fun getFitIntent(context: Context): Intent {
val pm: PackageManager = context.packageManager
return try {
pm.getLaunchIntentForPackage("com.google.android.apps.fitness")!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}
} catch (e: Exception) {
context.toast(context.getString(R.string.error_opening_app))
Intent()
}
}
}

View File

@ -0,0 +1,92 @@
package com.tommasoberlose.anotherwidget.helpers
import android.app.Notification
import android.content.ComponentName
import android.content.Context
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import com.chibatching.kotpref.Kotpref
import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.MusicNotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import java.lang.Exception
object MediaPlayerHelper {
fun isSomeonePlaying(context: Context) = Preferences.showMusic && NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName) && Preferences.mediaPlayerTitle != ""
fun getMediaInfo(): String {
return if (Preferences.mediaPlayerArtist == "") {
Preferences.mediaPlayerTitle
} else {
"%s, %s".format(Preferences.mediaPlayerTitle, Preferences.mediaPlayerArtist)
}
}
fun updatePlayingMediaInfo(context: Context) {
Kotpref.init(context)
if (NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName)) {
val list = try {
(context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager).getActiveSessions(
ComponentName(context.packageName, MusicNotificationListener::class.java.name)
)
} catch (ex: Exception) {
emptyList<MediaController>()
}
if (list.isNotEmpty()) {
var isSomeonePlaying = false
list.forEach { mc ->
val metadata = mc.metadata
val isPlaying =
mc.playbackState?.state == PlaybackState.STATE_PLAYING || mc.playbackState?.state == PlaybackState.STATE_CONNECTING
if (isPlaying) {
isSomeonePlaying = true
if (metadata != null) {
Preferences.bulk {
mediaPlayerTitle =
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)?.toString()
?: ""
mediaPlayerArtist =
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)?.toString()
?: ""
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)?.toString()
?: ""
}
}
Preferences.mediaPlayerPackage = mc.packageName
}
}
if (!isSomeonePlaying) {
removeMediaInfo(context)
}
} else {
removeMediaInfo(context)
}
} else {
removeMediaInfo(context)
}
MainWidget.updateWidget(context)
}
private fun removeMediaInfo(context: Context) {
Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::mediaPlayerTitle)
remove(Preferences::mediaPlayerArtist)
remove(Preferences::mediaPlayerAlbum)
remove(Preferences::mediaPlayerPackage)
}
}
}

View File

@ -3,6 +3,8 @@ package com.tommasoberlose.anotherwidget.helpers
import android.content.Context
import android.text.format.DateUtils
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import org.joda.time.DateTime
import java.util.concurrent.TimeUnit
@ -39,7 +41,6 @@ object SettingsStringHelper {
return when (info) {
0 -> R.string.settings_second_row_info_subtitle_0
1 -> R.string.settings_second_row_info_subtitle_1
2 -> R.string.settings_second_row_info_subtitle_2
else -> R.string.settings_second_row_info_subtitle_0
}
}
@ -69,11 +70,38 @@ object SettingsStringHelper {
difference += 60 * 1000 - (difference % (60 * 1000))
when {
difference <= 0 || TimeUnit.MILLISECONDS.toHours(difference) < 1 -> {
difference <= 0 -> {
return ""
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.value && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.value && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.LOW.value -> {
return context.getString(R.string.soon)
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 -> {
return context.getString(R.string.now)
}
TimeUnit.MILLISECONDS.toHours(difference) < 12 -> {
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.HOUR_IN_MILLIS).toString()
val minutes = TimeUnit.MILLISECONDS.toMinutes(difference) - 60 * TimeUnit.MILLISECONDS.toHours(difference)
return if (minutes < 1 || minutes > 30) {
DateUtils.getRelativeTimeSpanString(
start,
now - 1000 * 60 * 40,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
} else {
DateUtils.getRelativeTimeSpanString(
start,
now,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
}
}
eventDate.dayOfYear == nowDate.plusDays(1).dayOfYear -> {
return String.format("%s", context.getString(R.string.tomorrow))
@ -82,7 +110,7 @@ object SettingsStringHelper {
return String.format("%s", context.getString(R.string.today))
}
else -> {
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.DAY_IN_MILLIS).toString()
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
}

View File

@ -3,17 +3,15 @@ package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.content.Context
import android.os.Build
import android.util.Log
import com.google.android.gms.location.LocationServices
import com.kwabenaberko.openweathermaplib.constants.Units
import com.kwabenaberko.openweathermaplib.implementation.OpenWeatherMapHelper
import com.kwabenaberko.openweathermaplib.implementation.callbacks.CurrentWeatherCallback
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.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import org.greenrobot.eventbus.EventBus
/**
@ -35,6 +33,7 @@ object WeatherHelper {
Preferences.customLocationLon = location.longitude.toString()
networkApi.updateWeather()
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
@ -47,82 +46,202 @@ object WeatherHelper {
MainWidget.updateWidget(context)
}
fun getWeatherIconResource(icon: String): Int {
when (icon) {
fun getWeatherIconResource(icon: String, style: Int = Preferences.weatherIconPack): Int {
return when (icon) {
"01d" -> {
return R.drawable.clear_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_day_4
else -> R.drawable.clear_day
}
}
"02d" -> {
return R.drawable.partly_cloudy
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_4
else -> R.drawable.partly_cloudy
}
}
"03d" -> {
return R.drawable.mostly_cloudy
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_4
else -> R.drawable.mostly_cloudy
}
}
"04d" -> {
return R.drawable.cloudy_weather
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
else -> R.drawable.cloudy_weather
}
}
"09d" -> {
return R.drawable.storm_weather_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_day_4
else -> R.drawable.storm_weather_day
}
}
"10d" -> {
return R.drawable.rainy_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_day_4
else -> R.drawable.rainy_day
}
}
"11d" -> {
return R.drawable.thunder_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_day_4
else -> R.drawable.thunder_day
}
}
"13d" -> {
return R.drawable.snow_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_day_4
else -> R.drawable.snow_day
}
}
"50d" -> {
return R.drawable.haze_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_day_4
else -> R.drawable.haze_day
}
}
"80d" -> {
return R.drawable.windy_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_day_4
else -> R.drawable.windy_day
}
}
"81d" -> {
return R.drawable.rain_snow_day
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_day_4
else -> R.drawable.rain_snow_day
}
}
"82d" -> {
return R.drawable.haze_weather
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
else -> R.drawable.haze_weather
}
}
"01n" -> {
return R.drawable.clear_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_night_4
else -> R.drawable.clear_night
}
}
"02n" -> {
return R.drawable.partly_cloudy_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_night_4
else -> R.drawable.partly_cloudy_night
}
}
"03n" -> {
return R.drawable.mostly_cloudy_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_night_4
else -> R.drawable.mostly_cloudy_night
}
}
"04n" -> {
return R.drawable.cloudy_weather
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
else -> R.drawable.cloudy_weather
}
}
"09n" -> {
return R.drawable.storm_weather_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_night_4
else -> R.drawable.storm_weather_night
}
}
"10n" -> {
return R.drawable.rainy_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_night_4
else -> R.drawable.rainy_night
}
}
"11n" -> {
return R.drawable.thunder_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_night_4
else -> R.drawable.thunder_night
}
}
"13n" -> {
return R.drawable.snow_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_night_4
else -> R.drawable.snow_night
}
}
"50n" -> {
return R.drawable.haze_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_night_4
else -> R.drawable.haze_night
}
}
"80n" -> {
return R.drawable.windy_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_night_4
else -> R.drawable.windy_night
}
}
"81n" -> {
return R.drawable.rain_snow_night
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_night_4
else -> R.drawable.rain_snow_night
}
}
"82n" -> {
return R.drawable.haze_weather
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
else -> R.drawable.haze_weather
}
}
else -> {
return R.drawable.unknown

View File

@ -3,6 +3,10 @@ package com.tommasoberlose.anotherwidget.helpers
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
object WidgetHelper {
class WidgetSizeProvider(
@ -16,22 +20,14 @@ object WidgetHelper {
val height = getWidgetHeight(isPortrait, widgetId)
val widthInPx = context.dip(width)
val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
FirebaseCrashlytics.getInstance().setCustomKey("heightInPx", heightInPx)
return widthInPx to heightInPx
}
private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int =
if (isPortrait) {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
} else {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
}
private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int =
if (isPortrait) {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
} else {
getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
}
private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int = getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
private fun getWidgetSizeInDp(widgetId: Int, key: String): Int =
appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0)

View File

@ -1,5 +1,6 @@
package com.tommasoberlose.anotherwidget.models
import android.provider.CalendarContract
import io.realm.RealmObject
import java.util.Date
@ -7,15 +8,19 @@ import java.util.Date
* Created by tommaso on 05/10/17.
*/
open class Event(var id: Long = 0,
var eventID: Long = 0,
var title: String = "",
var startDate: Long = 0,
var endDate: Long = 0,
var calendarID: Int = 0,
var allDay: Boolean = false,
var address: String = "") : RealmObject() {
open class Event(
var id: Long = 0,
var eventID: Long = 0,
var title: String = "",
var startDate: Long = 0,
var endDate: Long = 0,
var calendarID: Int = 0,
var allDay: Boolean = false,
var address: String = "",
var selfAttendeeStatus: Int = CalendarContract.Attendees.ATTENDEE_STATUS_NONE,
var availability: Int = CalendarContract.EventsEntity.AVAILABILITY_BUSY
) : RealmObject() {
override fun toString(): String {
return "Event:\nID: " + 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,7 @@
package com.tommasoberlose.anotherwidget.models
class GlanceProvider(
val id: String,
val title: String,
val icon: Int
)

View File

@ -1,14 +1,13 @@
package com.tommasoberlose.anotherwidget.network
import android.content.Context
import android.util.Log
import com.kwabenaberko.openweathermaplib.constants.Units
import com.kwabenaberko.openweathermaplib.implementation.OpenWeatherMapHelper
import com.kwabenaberko.openweathermaplib.implementation.callbacks.CurrentWeatherCallback
import com.kwabenaberko.openweathermaplib.models.currentweather.CurrentWeather
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import org.greenrobot.eventbus.EventBus
@ -26,7 +25,7 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainActivity.UpdateUiMessageEvent())
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}

View File

@ -0,0 +1,180 @@
package com.tommasoberlose.anotherwidget.receivers
import android.Manifest
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.chibatching.kotpref.Kotpref
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.DataType
import com.google.android.gms.fitness.data.Field.FIELD_STEPS
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.location.*
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.*
import java.util.concurrent.TimeUnit
class ActivityDetectionReceiver : 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 && lastEvent.transitionType == ActivityTransition.ACTIVITY_TRANSITION_EXIT) {
requestDailySteps(context)
}
} else {
if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_MY_PACKAGE_REPLACED && Preferences.showDailySteps && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION)) {
resetDailySteps(context)
registerFence(context)
} else {
resetDailySteps(context)
}
}
}
private fun resetDailySteps(context: Context) {
Kotpref.init(context)
Preferences.googleFitSteps = -1
}
companion object {
val FITNESS_OPTIONS: FitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.build()
fun registerFence(context: Context) {
Kotpref.init(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_EXIT)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.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, ActivityDetectionReceiver::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, ActivityDetectionReceiver::class.java),
0
)
)
task.addOnCompleteListener {
if (it.isSuccessful) {
PendingIntent.getBroadcast(
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
).cancel()
}
}
}
fun requestDailySteps(context: Context) {
Kotpref.init(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 {
try {
it.getDataSet(DataType.AGGREGATE_STEP_COUNT_DELTA)?.dataPoints?.get(
0
)?.getValue(FIELD_STEPS)?.asInt() ?: 0
} catch (ex: Exception) {
0
}
}.toLong()
MainWidget.updateWidget(context)
setTimeout(context)
}
}
}
private fun setTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 5, Intent(context, ActivityDetectionReceiver::class.java), 0))
setExactAndAllowWhileIdle(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + 5 * 60 * 1000,
PendingIntent.getBroadcast(
context,
5,
Intent(context, ActivityDetectionReceiver::class.java),
0
)
)
}
}
}
}

View File

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

View File

@ -0,0 +1,32 @@
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

@ -0,0 +1,37 @@
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

@ -24,5 +24,6 @@ class NewCalendarEventReceiver : BroadcastReceiver() {
eventRepository.goToPreviousEvent()
}
}
eventRepository.close()
}
}

View File

@ -10,7 +10,11 @@ import androidx.core.app.AlarmManagerCompat
import androidx.core.content.ContextCompat.getSystemService
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.BatteryHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import org.joda.time.Period
import java.util.*
@ -19,70 +23,154 @@ import java.util.*
class UpdatesReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("ciao", "che palle - ${intent.action}")
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED,
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> CalendarHelper.updateEventList(context)
Intent.ACTION_DATE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> {
CalendarHelper.updateEventList(context)
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
Intent.ACTION_DATE_CHANGED,
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_TIME_UPDATE -> {
Log.d("ciao", "force update? 4 - ${intent.action}")
MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) {
setUpdates(context, intent.getLongExtra(EVENT_ID, -1))
}
}
}
}
companion object {
const val EVENT_ID = "EVENT_ID"
fun setUpdates(context: Context) {
removeUpdates(context)
fun setUpdates(context: Context, eventId: Long? = null) {
val eventRepository = EventRepository(context)
if (eventId == null) {
removeUpdates(context)
eventRepository.getFutureEvents().forEach { event ->
setEventUpdate(context, event)
}
} else {
val event = eventRepository.getEventByEventId(eventId)
if (event != null) {
setEventUpdate(context, event)
}
}
eventRepository.close()
}
private fun setEventUpdate(context: Context, event: Event) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
eventRepository.getEvents().forEach { event ->
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate)
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate)
val limit = when (Preferences.showUntil) {
0 -> 1000 * 60 * 60 * 3
1 -> 1000 * 60 * 60 * 6
2 -> 1000 * 60 * 60 * 12
3 -> 1000 * 60 * 60 * 24
4 -> 1000 * 60 * 60 * 24 * 3
5 -> 1000 * 60 * 60 * 24 * 7
6 -> 1000 * 60 * 30
7 -> 1000 * 60 * 60
else -> 1000 * 60 * 60 * 6
}
if (event.startDate <= limit) {
if (event.startDate > now.timeInMillis) {
// Update the widget every hour till the event
(0..diff.hours).forEach {
AlarmManagerCompat.setExactAndAllowWhileIdle(
this,
AlarmManager.RTC_WAKEUP,
if (event.startDate - it * 1000 * 60 * 60 > 60 * 1000) event.startDate - it * 1000 * 60 * 60 else 120000,
if (diff.hours == 0) {
var minutes = 0
when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.value -> {
minutes = when {
diff.minutes > 50 -> 50
diff.minutes > 30 -> 30
diff.minutes > 15 -> 15
else -> 0
}
}
Constants.WidgetUpdateFrequency.HIGH.value -> {
minutes = diff.minutes - (diff.minutes % 5)
}
}
setExact(
AlarmManager.RTC,
if (event.startDate - minutes * 1000 * 60 > (now.timeInMillis + 120 * 1000)) event.startDate - 60 * 1000 * minutes else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
0,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
0
PendingIntent.FLAG_UPDATE_CURRENT
)
)
} else {
setExact(
AlarmManager.RTC,
event.startDate - diff.hours * 1000 * 60 * 60 + if (diff.minutes > 30) (-30) else (+30),
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
} else {
// Update the widget one second after the event is finished
val fireTime =
if (event.endDate > now.timeInMillis + 120 * 1000) event.endDate else now.timeInMillis + 120000
setExact(
AlarmManager.RTC,
fireTime,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
},
0
)
)
}
// Update the widget one second after the event is finished
AlarmManagerCompat.setExactAndAllowWhileIdle(this,
AlarmManager.RTC_WAKEUP,
if (event.endDate > 60 *1000) event.endDate else 120000,
PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply { action = Actions.ACTION_TIME_UPDATE }, 0))
} else {
setExact(
AlarmManager.RTC,
if (event.startDate - limit > now.timeInMillis + 120 * 1000) event.startDate - limit else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
}
}
fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java), 0))
val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java), 0))
}
eventRepository.close()
}
}
}

View File

@ -5,10 +5,10 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import java.util.*
@ -20,7 +20,61 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED -> WeatherWorker.setUpdates(context)
Intent.ACTION_TIME_CHANGED -> setUpdates(context)
Actions.ACTION_WEATHER_UPDATE -> {
WeatherHelper.updateWeather(context)
}
}
}
companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) {
removeUpdates(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) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
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

@ -0,0 +1,61 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
class BatteryListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
MainWidget.updateWidget(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val chargingJobId = 1006
private const val notChargingJobId = 1007
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
remove(context)
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(chargingJobId, componentName)
.setRequiresCharging(true)
.setPersisted(true)
.build()
)
schedule(
JobInfo.Builder(notChargingJobId, componentName)
.setRequiresCharging(false)
.setPersisted(true)
.build()
)
}
}
}
private fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(chargingJobId)
js?.cancel(notChargingJobId)
}
}
}

View File

@ -0,0 +1,143 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import android.util.Log
import androidx.core.app.JobIntentService
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
class UpdateCalendarJob : JobIntentService() {
companion object {
private const val jobId = 1200
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, UpdateCalendarJob::class.java, jobId, work)
}
}
override fun onHandleWork(intent: Intent) {
val eventRepository = EventRepository(this)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
val now = Calendar.getInstance()
val begin = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
}
val limit = Calendar.getInstance().apply {
timeInMillis = begin.timeInMillis
add(Calendar.DAY_OF_YEAR, 2)
}
if (!checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
} else {
try {
val provider = CalendarProvider(this)
val data = provider.getInstances(begin.timeInMillis, limit.timeInMillis)
if (data != null) {
val instances = data.list
for (instance in instances) {
try {
val e = provider.getEvent(instance.eventId)
if (e != null && !e.deleted && instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end && !CalendarHelper.getFilteredCalendarIdList()
.contains(e.calendarId)
) {
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId.toInt(),
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val filteredEventList = eventList
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventList.sortWith(Comparator { event: Event, event1: Event ->
val date = Calendar.getInstance().apply { timeInMillis = event.startDate }
val date1 = Calendar.getInstance().apply { timeInMillis = event1.startDate }
if (date.get(Calendar.DAY_OF_YEAR) == date1.get(Calendar.DAY_OF_YEAR) && date.get(Calendar.YEAR) == date1.get(Calendar.YEAR)) {
if (event.allDay && event1.allDay) {
event1.startDate.compareTo(event.startDate)
} else if (event.allDay) {
-1
} else if (event1.allDay) {
1
} else {
event1.startDate.compareTo(event.startDate)
}
} else {
event1.startDate.compareTo(event.startDate)
}
})
eventList.reverse()
eventRepository.saveEvents(
eventList
)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
}
UpdatesReceiver.setUpdates(this)
MainWidget.updateWidget(this)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
eventRepository.close()
}
}

View File

@ -1,64 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.*
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import java.util.*
import java.util.concurrent.TimeUnit
class WeatherWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
Log.d("ciao1", "weather ok")
WeatherHelper.updateWeather(applicationContext)
return Result.success()
}
companion object {
private const val JOB_TAG = "WEATHER_WORKER"
fun setUpdates(context: Context) {
removeUpdates(context)
if (Preferences.showWeather && Preferences.weatherProviderApi != "") {
WeatherHelper.updateWeather(context)
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"WEATHER_JOB_PERIODIC",
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequestBuilder<WeatherWorker>(
when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
, TimeUnit.MINUTES
)
.addTag(JOB_TAG)
.build()
)
}
}
fun setOneTimeUpdate(context: Context) {
val workManager = WorkManager.getInstance(context)
listOf(10L, 20L, 30L).forEach {
workManager.enqueueUniqueWork("WEATHER_JOB_ONE_TIME_$it", ExistingWorkPolicy.KEEP, OneTimeWorkRequestBuilder<WeatherWorker>().setInitialDelay(it, TimeUnit.MINUTES).addTag(JOB_TAG).build())
}
}
fun removeUpdates(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(JOB_TAG)
}
}
}

View File

@ -6,19 +6,23 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomDateBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.CustomDateViewModel
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.activity_custom_date.*
import kotlinx.android.synthetic.main.activity_custom_location.action_back
import kotlinx.android.synthetic.main.activity_custom_location.list_view
@ -78,7 +82,7 @@ class CustomDateActivity : AppCompatActivity() {
}
delay(200)
val text = if (dateFormat != "") {
var text = if (dateFormat != "") {
try {
SimpleDateFormat(dateFormat, Locale.getDefault()).format(DATE.time)
} catch (e: Exception) {
@ -88,6 +92,14 @@ class CustomDateActivity : AppCompatActivity() {
"__"
}
if (viewModel.isDateCapitalize.value == true) {
text = text.getCapWordString()
}
if (viewModel.isDateUppercase.value == true) {
text = text.toUpperCase(Locale.getDefault())
}
withContext(Dispatchers.Main) {
action_save.isVisible = text != ERROR_STRING
loader.visibility = View.INVISIBLE
@ -96,6 +108,33 @@ class CustomDateActivity : AppCompatActivity() {
}
})
viewModel.isDateCapitalize.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
viewModel.isDateUppercase.observe(this, Observer {
viewModel.dateInput.value = viewModel.dateInput.value
updateCapitalizeUi()
})
}
private fun updateCapitalizeUi() {
when {
viewModel.isDateUppercase.value == true -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
action_capitalize.alpha = 1f
}
viewModel.isDateCapitalize.value == true -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.ic_capitalize))
action_capitalize.alpha = 1f
}
else -> {
action_capitalize.setImageDrawable(ContextCompat.getDrawable(this@CustomDateActivity, R.drawable.round_publish))
action_capitalize.alpha = 0.3f
}
}
}
private fun setupListener() {
@ -104,10 +143,36 @@ class CustomDateActivity : AppCompatActivity() {
}
action_save.setOnClickListener {
Preferences.dateFormat = viewModel.dateInput.value ?: ""
Preferences.blockingBulk {
dateFormat = viewModel.dateInput.value ?: ""
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
finish()
}
action_capitalize.setOnClickListener {
when {
viewModel.isDateUppercase.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = false
}
viewModel.isDateCapitalize.value == true -> {
viewModel.isDateCapitalize.value = false
viewModel.isDateUppercase.value = true
}
else -> {
viewModel.isDateCapitalize.value = true
viewModel.isDateUppercase.value = false
}
}
}
action_capitalize.setOnLongClickListener {
toast(getString(R.string.action_capitalize_the_date))
true
}
action_date_format_info.setOnClickListener {
openURI("https://developer.android.com/reference/java/text/SimpleDateFormat")
}

View File

@ -28,6 +28,7 @@ import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomLocationBinding
import com.tommasoberlose.anotherwidget.global.Preferences
@ -62,7 +63,11 @@ class CustomLocationActivity : AppCompatActivity() {
injector
.text(R.id.text, getString(R.string.custom_location_gps))
.clicked(R.id.text) {
requirePermission()
MaterialBottomSheetDialog(this, message = getString(R.string.background_location_warning))
.setPositiveButton(getString(android.R.string.ok)) {
requirePermission()
}
.show()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->

View File

@ -0,0 +1,52 @@
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

@ -6,12 +6,15 @@ import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Matrix
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
@ -21,8 +24,16 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.Navigation
import com.chibatching.kotpref.Kotpref
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.tabs.TabLayoutMediator
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.global.Actions
@ -47,10 +58,16 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
class MainActivity : AppCompatActivity() {
private var mAppWidgetId: Int = -1
private lateinit var viewModel: MainViewModel
private val mainNavController: NavController? by lazy {
Navigation.findNavController(
this,
R.id.content_fragment
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -59,228 +76,21 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
controlExtras(intent)
// Viewpager
pager.adapter = ViewPagerAdapter(this)
pager.offscreenPageLimit = 4
TabLayoutMediator(tabs, pager) { tab, position ->
tab.text = when (position) {
0 -> getString(R.string.settings_general_title)
1 -> getString(R.string.settings_calendar_title)
2 -> getString(R.string.settings_weather_title)
3 -> getString(R.string.settings_clock_title)
4 -> getString(R.string.advanced_settings_title)
else -> ""
}
}.attach()
// Init clock
time.setTextColor(ColorHelper.getFontColor())
time.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(this@MainActivity))
time_am_pm.setTextColor(ColorHelper.getFontColor())
time_am_pm.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(this@MainActivity) / 5 * 2)
time_container.isVisible = Preferences.showClock
preview.layoutParams = preview.layoutParams.apply {
height = 160.toPixel(this@MainActivity) + if (Preferences.showClock) 100.toPixel(this@MainActivity) else 0
}
Preferences.preferences.registerOnSharedPreferenceChangeListener(this)
subscribeUi(viewModel)
updateUI()
WeatherHelper.updateWeather(this)
// Warnings
if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) {
MaterialBottomSheetDialog(this, getString(R.string.xiaomi_warning_title), getString(R.string.xiaomi_warning_message))
.setNegativeButton(getString(R.string.action_ignore)) {
Preferences.showXiaomiWarning = false
}
.setPositiveButton(getString(R.string.action_grant_permission)) {
Preferences.showXiaomiWarning = false
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:$packageName")
}
startActivity(intent)
}
.show()
}
}
private var uiJob: Job? = null
private fun updateUI() {
uiJob?.cancel()
if (Preferences.showPreview) {
preview.setCardBackgroundColor(
getColor(
if (ColorHelper.getFontColor()
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
)
widget_shape_background.setImageDrawable(BitmapHelper.getTintedDrawable(this, R.drawable.card_background, ColorHelper.getBackgroundColor()))
uiJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
val generatedView = MainWidget.generateWidgetView(this@MainActivity)
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview.measure(0, 0)
}
val bitmap = BitmapHelper.getBitmapFromView(
generatedView,
if (preview.width > 0) preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
withContext(Dispatchers.Main) {
// Clock
time.setTextColor(ColorHelper.getFontColor())
time_am_pm.setTextColor(ColorHelper.getFontColor())
time.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(this@MainActivity)
)
time_am_pm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(this@MainActivity) / 5 * 2
)
// Clock bottom margin
clock_bottom_margin_none.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.value
clock_bottom_margin_small.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value
clock_bottom_margin_medium.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value
clock_bottom_margin_large.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value
if ((Preferences.showClock && !time_container.isVisible) || (!Preferences.showClock && time_container.isVisible)) {
if (Preferences.showClock) {
time_container.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container.measure(0, 0)
}
val initialHeight = time_container.measuredHeight
ValueAnimator.ofFloat(
if (Preferences.showClock) 0f else 1f,
if (Preferences.showClock) 1f else 0f
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
time_container.layoutParams = time_container.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
}
addListener(
onStart = {
if (Preferences.showClock) {
time_container.isVisible = true
}
},
onEnd = {
if (!Preferences.showClock) {
time_container.isVisible = false
}
}
)
}.start()
ValueAnimator.ofInt(
preview.height,
160.toPixel(this@MainActivity) + if (Preferences.showClock) 100.toPixel(
this@MainActivity
) else 0
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}.start()
} else {
time_container.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container.measure(0, 0)
}
if (preview.height == 0) {
ValueAnimator.ofInt(
preview.height,
160.toPixel(this@MainActivity) + if (Preferences.showClock) 100.toPixel(
this@MainActivity
) else 0
).apply {
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}.start()
}
bitmap_container.setImageBitmap(bitmap)
widget_loader.animate().scaleX(0f).scaleY(0f).start()
widget.animate().alpha(1f).start()
}
}
} else {
ValueAnimator.ofInt(
preview.height,
0
).apply {
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}.start()
}
// Calendar error indicator
tabs.getTabAt(1)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)
// Weather error indicator
tabs.getTabAt(2)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(this@MainActivity, R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showWeather && (Preferences.weatherProviderApi == "" || (Preferences.customLocationAdd == "" && !checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION)))
}
private fun subscribeUi(viewModel: MainViewModel) {
viewModel.showWallpaper.observe(this, Observer {
widget_bg.setImageDrawable(if (it) getCurrentWallpaper() else null)
})
logo.setOnClickListener {
// startActivity(Intent(this, SupportDevActivity::class.java))
if (Preferences.showWallpaper) {
requirePermission()
}
}
override fun onBackPressed() {
if (mAppWidgetId > 0) {
addNewWidget()
if (mainNavController?.currentDestination?.id == R.id.appMainFragment) {
if (mAppWidgetId > 0) {
addNewWidget()
} else {
setResult(Activity.RESULT_OK)
finish()
}
} else {
setResult(Activity.RESULT_OK)
finish()
super.onBackPressed()
}
}
@ -320,30 +130,27 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
finish()
}
override fun onDestroy() {
super.onDestroy()
Preferences.preferences.unregisterOnSharedPreferenceChangeListener(this)
}
private fun requirePermission() {
Dexter.withContext(this)
.withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
Preferences.showWallpaper = false
Preferences.showWallpaper = report.areAllPermissionsGranted()
}
}
override fun onSharedPreferenceChanged(preferences: SharedPreferences, p1: String) {
updateUI()
MainWidget.updateWidget(this)
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
class UpdateUiMessageEvent
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: UpdateUiMessageEvent?) {
updateUI()
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?.cancelPermissionRequest()
}
})
.check()
}
}

View File

@ -6,6 +6,7 @@ import android.location.Address
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
@ -45,8 +46,17 @@ class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
adapter = SlimAdapter.create()
adapter
.register<SkuDetails>(R.layout.inapp_product_layout) { item, injector ->
item.sku
injector
.text(R.id.product_title, item.title.replace("(Another Widget)", ""))
.with<TextView>(R.id.product_title) {
it.text = when (item.sku) {
"donation_coffee" -> getString(R.string.donation_coffee)
"donation_donuts" -> getString(R.string.donation_donuts)
"donation_breakfast" -> getString(R.string.donation_breakfast)
"donation_lunch" -> getString(R.string.donation_lunch)
else -> ""
}
}
.text(R.id.product_price, item.price)
.clicked(R.id.item) {
viewModel.purchase(this, item)

View File

@ -32,11 +32,6 @@ class WeatherProviderActivity : AppCompatActivity() {
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

@ -12,11 +12,11 @@ class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
override fun createFragment(position: Int): Fragment {
return when (position) {
1 -> CalendarSettingsFragment.newInstance()
2 -> WeatherSettingsFragment.newInstance()
3 -> ClockSettingsFragment.newInstance()
4 -> AdvancedSettingsFragment.newInstance()
else -> GeneralSettingsFragment.newInstance()
1 -> CalendarTabFragment.newInstance()
2 -> WeatherTabFragment.newInstance()
3 -> ClockTabFragment.newInstance()
4 -> GlanceTabFragment.newInstance()
else -> GeneralTabFragment.newInstance()
}
}
}

View File

@ -4,10 +4,10 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.provider.CalendarContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
@ -31,22 +31,21 @@ import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.activities.CustomDateActivity
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDefaultSet
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.fragment_calendar_settings.*
import kotlinx.android.synthetic.main.fragment_calendar_settings.scrollView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import kotlin.Comparator
class CalendarSettingsFragment : Fragment() {
class CalendarTabFragment : Fragment() {
companion object {
fun newInstance() = CalendarSettingsFragment()
fun newInstance() = CalendarTabFragment()
}
private lateinit var viewModel: MainViewModel
@ -74,6 +73,11 @@ class CalendarSettingsFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
show_all_day_toggle.isChecked = Preferences.calendarAllDay
show_only_busy_events_toggle.isChecked = Preferences.showOnlyBusyEvents
show_diff_time_toggle.isChecked = Preferences.showDiffTime
show_multiple_events_toggle.isChecked = Preferences.showNextEvent
setupListener()
}
@ -81,6 +85,9 @@ class CalendarSettingsFragment : Fragment() {
binding: FragmentCalendarSettingsBinding,
viewModel: MainViewModel
) {
binding.isCalendarEnabled = Preferences.showEvents
binding.isDiffEnabled = Preferences.showDiffTime || !Preferences.showEvents
viewModel.showEvents.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
binding.isCalendarEnabled = it
@ -90,23 +97,17 @@ class CalendarSettingsFragment : Fragment() {
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
}
binding.isDiffEnabled = Preferences.showDiffTime || !it
}
checkReadEventsPermission()
updateCalendar()
})
viewModel.calendarAllDay.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
all_day_label?.text =
if (it) getString(R.string.settings_all_day_subtitle_visible) else getString(R.string.settings_all_day_subtitle_gone)
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
checkReadEventsPermission()
})
viewModel.showDeclinedEvents.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_declined_events_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
checkReadEventsPermission()
})
viewModel.secondRowInformation.observe(viewLifecycleOwner, Observer {
@ -118,6 +119,18 @@ class CalendarSettingsFragment : Fragment() {
viewModel.showDiffTime.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_diff_time_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
binding.isDiffEnabled = it || !Preferences.showEvents
}
})
viewModel.widgetUpdateFrequency.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
widget_update_frequency_label?.text = when (it) {
Constants.WidgetUpdateFrequency.HIGH.value -> getString(R.string.settings_widget_update_frequency_high)
Constants.WidgetUpdateFrequency.DEFAULT.value -> getString(R.string.settings_widget_update_frequency_default)
Constants.WidgetUpdateFrequency.LOW.value -> getString(R.string.settings_widget_update_frequency_low)
else -> ""
}
}
})
@ -125,22 +138,30 @@ class CalendarSettingsFragment : Fragment() {
maintainScrollPosition {
show_until_label?.text = getString(SettingsStringHelper.getShowUntilString(it))
}
checkReadEventsPermission()
updateCalendar()
})
viewModel.showNextEvent.observe(viewLifecycleOwner, Observer {
show_multiple_events_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
})
viewModel.dateFormat.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
date_format_label?.text = DateHelper.getDateText(requireContext(), Calendar.getInstance())
show_multiple_events_label?.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.calendarAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
calendar_app_label?.text = if (it != "") it else getString(R.string.default_calendar_app)
calendar_app_label?.text = when {
Preferences.clockAppName != "" -> Preferences.clockAppName
else -> {
if (IntentHelper.getCalendarIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_calendar_app
)
} else {
getString(R.string.nothing)
}
}
}
}
})
@ -156,6 +177,16 @@ class CalendarSettingsFragment : Fragment() {
action_show_events.setOnClickListener {
Preferences.showEvents = !Preferences.showEvents
if (Preferences.showEvents) {
requirePermission()
}
}
show_events_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showEvents = enabled
if (Preferences.showEvents) {
requirePermission()
}
}
action_filter_calendar.setOnClickListener {
@ -195,14 +226,14 @@ class CalendarSettingsFragment : Fragment() {
}
dialog.addItem(
if (calendarSelectorList[index].name == calendarSelectorList[index].accountName) getString(R.string.account_events) else calendarSelectorList[index].name,
if (calendarSelectorList[index].name == calendarSelectorList[index].accountName) getString(R.string.main_calendar) else calendarSelectorList[index].name,
calendarSelectorList[index].id
)
}
dialog.addOnMultipleSelectItemListener { values ->
CalendarHelper.filterCalendar(calendarSelectorList.map { it.id }.filter { !values.contains(it) })
checkReadEventsPermission()
updateCalendar()
}.show()
} else {
activity?.toast(getString(R.string.calendar_settings_list_error))
@ -211,44 +242,100 @@ class CalendarSettingsFragment : Fragment() {
action_show_all_day.setOnClickListener {
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_all_day_title)).setSelectedValue(Preferences.calendarAllDay)
.addItem(getString(R.string.settings_all_day_subtitle_visible), true)
.addItem(getString(R.string.settings_all_day_subtitle_gone), false)
.addOnSelectItemListener { value ->
Preferences.calendarAllDay = value
}.show()
show_all_day_toggle.isChecked = !show_all_day_toggle.isChecked
}
}
action_show_declined_events.setOnClickListener {
show_all_day_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_show_declined_events_title)).setSelectedValue(Preferences.showDeclinedEvents)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showDeclinedEvents = value
}.show()
Preferences.calendarAllDay = isChecked
updateCalendar()
}
}
action_change_attendee_filter.setOnClickListener {
if (Preferences.showEvents) {
val selectedValues = emptyList<Int>().toMutableList()
if (Preferences.showDeclinedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
}
if (Preferences.showInvitedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
}
if (Preferences.showAcceptedEvents) {
selectedValues.add(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
}
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_attendee_status_title), isMultiSelection = true)
.setSelectedValues(selectedValues)
dialog.addItem(
getString(R.string.attendee_status_invited),
CalendarContract.Attendees.ATTENDEE_STATUS_INVITED
)
dialog.addItem(
getString(R.string.attendee_status_accepted),
CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED
)
dialog.addItem(
getString(R.string.attendee_status_declined),
CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED
)
dialog.addOnMultipleSelectItemListener { values ->
Preferences.showDeclinedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
Preferences.showAcceptedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED)
Preferences.showInvitedEvents = values.contains(CalendarContract.Attendees.ATTENDEE_STATUS_INVITED)
updateCalendar()
}.show()
}
}
action_show_only_busy_events.setOnClickListener {
if (Preferences.showEvents) {
show_only_busy_events_toggle.isChecked = !show_only_busy_events_toggle.isChecked
}
}
show_only_busy_events_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showOnlyBusyEvents = isChecked
updateCalendar()
}
}
action_show_multiple_events.setOnClickListener {
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_show_multiple_events_title)).setSelectedValue(Preferences.showNextEvent)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showNextEvent = value
}.show()
show_multiple_events_toggle.isChecked = !show_multiple_events_toggle.isChecked
}
}
show_multiple_events_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showNextEvent = isChecked
}
}
action_show_diff_time.setOnClickListener {
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_show_diff_time_title)).setSelectedValue(Preferences.showDiffTime)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
show_diff_time_toggle.isChecked = !show_diff_time_toggle.isChecked
}
}
show_diff_time_toggle.setOnCheckedChangeListener { _, isChecked ->
if (Preferences.showEvents) {
Preferences.showDiffTime = isChecked
}
}
action_widget_update_frequency.setOnClickListener {
if (Preferences.showEvents && Preferences.showDiffTime) {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_widget_update_frequency_title), message = getString(R.string.settings_widget_update_frequency_subtitle)).setSelectedValue(Preferences.widgetUpdateFrequency)
.addItem(getString(R.string.settings_widget_update_frequency_high), Constants.WidgetUpdateFrequency.HIGH.value)
.addItem(getString(R.string.settings_widget_update_frequency_default), Constants.WidgetUpdateFrequency.DEFAULT.value)
.addItem(getString(R.string.settings_widget_update_frequency_low), Constants.WidgetUpdateFrequency.LOW.value)
.addOnSelectItemListener { value ->
Preferences.showDiffTime = value
Preferences.widgetUpdateFrequency = value
}.show()
}
}
@ -268,7 +355,7 @@ class CalendarSettingsFragment : Fragment() {
action_show_until.setOnClickListener {
if (Preferences.showEvents) {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_show_until_title)).setSelectedValue(Preferences.showUntil)
intArrayOf(6,7,0,1,2,3,4,5).forEach {
intArrayOf(6,7,0,1,2,3, 4, 5).forEach {
dialog.addItem(getString(SettingsStringHelper.getShowUntilString(it)), it)
}
dialog.addOnSelectItemListener { value ->
@ -277,27 +364,6 @@ class CalendarSettingsFragment : Fragment() {
}
}
action_date_format.setOnClickListener {
if (Preferences.showEvents) {
val now = Calendar.getInstance()
val dialog = BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_date_format_title)).setSelectedValue(Preferences.dateFormat)
dialog.addItem(DateHelper.getDefaultDateText(requireContext(), now), "")
if (Preferences.dateFormat != "") {
dialog.addItem(DateHelper.getDateText(requireContext(), now), Preferences.dateFormat)
}
dialog.addItem(getString(R.string.custom_date_format), "-")
dialog.addOnSelectItemListener { value ->
if (value == "-") {
startActivity(Intent(requireContext(), CustomDateActivity::class.java))
} else {
Preferences.dateFormat = value
}
}.show()
}
}
action_open_event_details.setOnClickListener {
if (Preferences.showEvents) {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_event_app_title)).setSelectedValue(Preferences.openEventDetails)
@ -318,7 +384,6 @@ class CalendarSettingsFragment : Fragment() {
if (activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) == true) {
show_events_label?.text = if (showEvents) getString(R.string.show_events_visible) else getString(R.string.show_events_not_visible)
read_calendar_permission_alert?.isVisible = false
CalendarHelper.updateEventList(requireContext())
} else {
show_events_label?.text = if (showEvents) getString(R.string.description_permission_calendar) else getString(R.string.show_events_not_visible)
read_calendar_permission_alert?.isVisible = showEvents
@ -328,6 +393,12 @@ class CalendarSettingsFragment : Fragment() {
}
}
private fun updateCalendar() {
if (activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) == true) {
CalendarHelper.updateEventList(requireContext())
}
}
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
@ -337,6 +408,8 @@ class CalendarSettingsFragment : Fragment() {
report?.let {
if (report.areAllPermissionsGranted()){
checkReadEventsPermission()
} else {
Preferences.showEvents = false
}
}
}
@ -373,11 +446,11 @@ class CalendarSettingsFragment : Fragment() {
}
private fun maintainScrollPosition(callback: () -> Unit) {
val scrollPosition = scrollView.scrollY
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.smoothScrollTo(0, scrollPosition)
scrollView.isScrollable = true
}
}
}

View File

@ -1,177 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentClockSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import kotlinx.android.synthetic.main.fragment_clock_settings.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ClockSettingsFragment : Fragment() {
companion object {
fun newInstance() = ClockSettingsFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentClockSettingsBinding>(inflater, R.layout.fragment_clock_settings, container, false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
}
private fun subscribeUi(
binding: FragmentClockSettingsBinding,
viewModel: MainViewModel
) {
viewModel.showBigClockWarning.observe(viewLifecycleOwner, Observer {
large_clock_warning?.isVisible = it
small_clock_warning?.isVisible = !it
})
viewModel.showClock.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_clock_label?.text =
if (it) getString(R.string.show_clock_visible) else getString(R.string.show_clock_not_visible)
binding.isClockVisible = it
}
})
viewModel.clockTextSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.clockBottomMargin.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_bottom_margin_label?.text = when (it) {
Constants.ClockBottomMargin.NONE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.ClockBottomMargin.SMALL.value -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.ClockBottomMargin.LARGE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
})
viewModel.showNextAlarm.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_next_alarm_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.clockAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
clock_app_label?.text =
if (Preferences.clockAppName != "") Preferences.clockAppName else getString(R.string.default_clock_app)
}
})
}
private fun setupListener() {
action_hide_large_clock_warning.setOnClickListener {
Preferences.showBigClockWarning = false
}
action_show_clock.setOnClickListener {
Preferences.showClock = !Preferences.showClock
}
action_clock_text_size.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.settings_clock_text_size_title)).setSelectedValue(Preferences.clockTextSize)
(46 downTo 12).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.clockTextSize = value
}.show()
}
action_clock_bottom_margin_size.setOnClickListener {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_show_next_alarm_title)).setSelectedValue(Preferences.clockBottomMargin)
.addItem(getString(R.string.settings_clock_bottom_margin_subtitle_none), Constants.ClockBottomMargin.NONE.value)
.addItem(getString(R.string.settings_clock_bottom_margin_subtitle_small), Constants.ClockBottomMargin.SMALL.value)
.addItem(getString(R.string.settings_clock_bottom_margin_subtitle_medium), Constants.ClockBottomMargin.MEDIUM.value)
.addItem(getString(R.string.settings_clock_bottom_margin_subtitle_large), Constants.ClockBottomMargin.LARGE.value)
.addOnSelectItemListener { value ->
Preferences.clockBottomMargin = value
}.show()
}
action_clock_app.setOnClickListener {
if (Preferences.showClock) {
startActivityForResult(Intent(requireContext(), ChooseApplicationActivity::class.java),
RequestCode.CLOCK_APP_REQUEST_CODE.code
)
}
}
action_show_next_alarm.setOnClickListener {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_show_next_alarm_title)).setSelectedValue(Preferences.showNextAlarm)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showNextAlarm = value
}.show()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == RequestCode.CLOCK_APP_REQUEST_CODE.code) {
Preferences.bulk {
clockAppName = data?.getStringExtra(Constants.RESULT_APP_NAME) ?: getString(R.string.default_clock_app)
clockAppPackage = data?.getStringExtra(Constants.RESULT_APP_PACKAGE) ?: ""
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) {
val scrollPosition = scrollView.scrollY
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.smoothScrollTo(0, scrollPosition)
}
}
}

View File

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

View File

@ -1,268 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentGeneralSettingsBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import kotlinx.android.synthetic.main.fragment_general_settings.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class GeneralSettingsFragment : Fragment() {
companion object {
fun newInstance() = GeneralSettingsFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentGeneralSettingsBinding>(inflater, R.layout.fragment_general_settings, container, false)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupListener()
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.textMainSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
main_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textSecondSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textGlobalColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor())).toUpperCase()
}
}
})
viewModel.textGlobalAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor())).toUpperCase()
}
}
})
viewModel.backgroundCardColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor())).toUpperCase()
}
}
})
viewModel.backgroundCardAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor())).toUpperCase()
}
}
})
viewModel.textShadow.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
text_shadow_label?.text = getString(SettingsStringHelper.getTextShadowString(it))
}
})
viewModel.customFont.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = getString(SettingsStringHelper.getCustomFontLabel(it))
}
})
}
private fun maintainScrollPosition(callback: () -> Unit) {
val scrollPosition = scrollView.scrollY
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.smoothScrollTo(0, scrollPosition)
}
}
private fun setupListener() {
action_main_text_size.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_main_text_size)).setSelectedValue(Preferences.textMainSize)
(32 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textMainSize = value
}.show()
}
action_second_text_size.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_second_text_size)).setSelectedValue(Preferences.textSecondSize)
(28 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textSecondSize = value
}.show()
}
action_font_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
selected = ColorHelper.getFontColor(),
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
Preferences.textGlobalColor = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
},
showAlphaSelector = true,
alpha = Preferences.textGlobalAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
Preferences.textGlobalAlpha = alpha.toHexValue()
}
).show()
}
action_background_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_background_color_title),
selected = ColorHelper.getBackgroundColor(),
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
Preferences.backgroundCardColor = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
},
showAlphaSelector = true,
alpha = Preferences.backgroundCardAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
Preferences.backgroundCardAlpha = alpha.toHexValue()
}
).show()
}
action_text_shadow.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.title_text_shadow)).setSelectedValue(Preferences.textShadow)
(2 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getTextShadowString(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.textShadow = value
}.show()
}
action_custom_font.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_custom_font_title)).setSelectedValue(Preferences.customFont)
(0..1).forEach {
dialog.addItem(getString(SettingsStringHelper.getCustomFontLabel(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.customFont = value
}.show()
/*
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "* / *" TO FIX WITHOUT SPACE
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(Intent.createChooser(intent, "Select a File to Upload"), Constants.CUSTOM_FONT_CHOOSER_REQUEST_CODE)
} catch (ex: android.content.ActivityNotFoundException) {
Toast.makeText(this, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
}
*/
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code -> {
/*val uri = data.data
Log.d("AW", "File Uri: " + uri.toString())
val path = Util.getPath(this, uri)
Log.d("AW", "File Path: " + path)
SP.edit()
.putString(Constants.PREF_CUSTOM_FONT_FILE, path)
.commit()
sendBroadcast(Intent(Constants.ACTION_TIME_UPDATE))
updateSettings()*/
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -0,0 +1,513 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.blockingBulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentGeneralSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.activities.CustomDateActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.android.synthetic.main.fragment_clock_settings.*
import kotlinx.android.synthetic.main.fragment_general_settings.*
import kotlinx.android.synthetic.main.fragment_general_settings.scrollView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class GeneralTabFragment : Fragment() {
companion object {
fun newInstance() = GeneralTabFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var colors: IntArray
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentGeneralSettingsBinding>(inflater, R.layout.fragment_general_settings, container, false)
subscribeUi(viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.isDarkModeEnabled = activity?.isDarkTheme() == true
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
show_dividers_toggle.isChecked = Preferences.showDividers
setupListener()
lifecycleScope.launch(Dispatchers.IO) {
val lazyColors = requireContext().resources.getIntArray(R.array.material_colors)
withContext(Dispatchers.Main) {
colors = lazyColors
}
}
}
@SuppressLint("DefaultLocale")
private fun subscribeUi(
viewModel: MainViewModel
) {
viewModel.textMainSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
main_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textSecondSize.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_text_size_label?.text = String.format("%.0fsp", it)
}
})
viewModel.textGlobalColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlphaDark == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlpha == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textGlobalAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textGlobalAlphaDark == "00") {
font_color_label?.text = getString(R.string.transparent)
} else {
font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlpha == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlphaDark == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlpha == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textSecondaryAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.textSecondaryAlphaDark == "00") {
secondary_font_color_label?.text = getString(R.string.transparent)
} else {
secondary_font_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getSecondaryFontColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.secondRowTopMargin.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
second_row_top_margin_label?.text = when (it) {
Constants.SecondRowTopMargin.NONE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_none)
Constants.SecondRowTopMargin.SMALL.value -> getString(R.string.settings_clock_bottom_margin_subtitle_small)
Constants.SecondRowTopMargin.LARGE.value -> getString(R.string.settings_clock_bottom_margin_subtitle_large)
else -> getString(R.string.settings_clock_bottom_margin_subtitle_medium)
}
}
})
viewModel.backgroundCardColor.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardColorDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlphaDark == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardAlpha.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlpha == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.backgroundCardAlphaDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (Preferences.backgroundCardAlphaDark == "00") {
background_color_label?.text = getString(R.string.transparent)
} else {
background_color_label?.text =
"#%s".format(Integer.toHexString(ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true))).toUpperCase()
}
}
})
viewModel.textShadow.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (activity?.isDarkTheme() != true) {
text_shadow_label?.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
})
viewModel.textShadowDark.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
if (activity?.isDarkTheme() == true) {
text_shadow_label?.text =
getString(SettingsStringHelper.getTextShadowString(it))
}
}
})
viewModel.dateFormat.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
date_format_label?.text = DateHelper.getDateText(requireContext(), Calendar.getInstance())
}
})
viewModel.customFont.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = getString(SettingsStringHelper.getCustomFontLabel(it))
}
})
viewModel.showDividers.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_dividers_label?.text =
if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
}
private fun setupListener() {
action_main_text_size.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_main_text_size)).setSelectedValue(Preferences.textMainSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textMainSize = value
}.show()
}
action_second_text_size.setOnClickListener {
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_second_text_size)).setSelectedValue(Preferences.textSecondSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
dialog.addOnSelectItemListener { value ->
Preferences.textSecondSize = value
}.show()
}
action_font_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_font_color_title),
getSelected = { ColorHelper.getFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.textGlobalColorDark = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textGlobalColor = "#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.textGlobalAlphaDark.toIntValue() else Preferences.textGlobalAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.textGlobalAlphaDark = alpha.toHexValue()
} else {
Preferences.textGlobalAlpha = alpha.toHexValue()
}
}
).show()
}
action_secondary_font_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_secondary_font_color_title),
getSelected = { ColorHelper.getSecondaryFontColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.textSecondaryColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.textSecondaryColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.textSecondaryAlphaDark.toIntValue() else Preferences.textSecondaryAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.textSecondaryAlphaDark = alpha.toHexValue()
} else {
Preferences.textSecondaryAlpha = alpha.toHexValue()
}
}
).show()
}
action_second_row_top_margin_size.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
header = getString(R.string.settings_secondary_row_top_margin_title)
).setSelectedValue(Preferences.secondRowTopMargin)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_none),
Constants.SecondRowTopMargin.NONE.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_small),
Constants.SecondRowTopMargin.SMALL.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_medium),
Constants.SecondRowTopMargin.MEDIUM.value
)
.addItem(
getString(R.string.settings_clock_bottom_margin_subtitle_large),
Constants.SecondRowTopMargin.LARGE.value
)
.addOnSelectItemListener { value ->
Preferences.secondRowTopMargin = value
}.show()
}
action_date_format.setOnClickListener {
val now = Calendar.getInstance()
val dialog = BottomSheetMenu<String>(requireContext(), header = getString(R.string.settings_date_format_title)).setSelectedValue(Preferences.dateFormat)
dialog.addItem(DateHelper.getDefaultDateText(requireContext(), now), "")
if (Preferences.dateFormat != "") {
dialog.addItem(DateHelper.getDateText(requireContext(), now), Preferences.dateFormat)
}
dialog.addItem(getString(R.string.custom_date_format), "-")
dialog.addOnSelectItemListener { value ->
when (value) {
"-" -> {
startActivity(Intent(requireContext(), CustomDateActivity::class.java))
}
"" -> {
Preferences.blockingBulk {
isDateCapitalize = false
isDateUppercase = false
}
Preferences.dateFormat = value
}
else -> {
Preferences.dateFormat = value
}
}
}.show()
}
action_background_color.setOnClickListener {
BottomSheetColorPicker(requireContext(),
colors = colors,
header = getString(R.string.settings_background_color_title),
getSelected = { ColorHelper.getBackgroundColorRgb(activity?.isDarkTheme() == true) },
onColorSelected = { color: Int ->
val colorString = Integer.toHexString(color)
if (activity?.isDarkTheme() == true) {
Preferences.backgroundCardColorDark =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
} else {
Preferences.backgroundCardColor =
"#" + if (colorString.length > 6) colorString.substring(2) else colorString
}
},
showAlphaSelector = true,
alpha = if (activity?.isDarkTheme() == true) Preferences.backgroundCardAlphaDark.toIntValue() else Preferences.backgroundCardAlpha.toIntValue(),
onAlphaChangeListener = { alpha ->
if (activity?.isDarkTheme() == true) {
Preferences.backgroundCardAlphaDark = alpha.toHexValue()
} else {
Preferences.backgroundCardAlpha = alpha.toHexValue()
}
}
).show()
}
action_text_shadow.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.title_text_shadow)).setSelectedValue(if (activity?.isDarkTheme() == true) Preferences.textShadowDark else Preferences.textShadow)
(2 downTo 0).forEach {
dialog.addItem(getString(SettingsStringHelper.getTextShadowString(it)), it)
}
dialog.addOnSelectItemListener { value ->
if (activity?.isDarkTheme() == true) {
Preferences.textShadowDark = value
} else {
Preferences.textShadow = value
}
}.show()
}
action_custom_font.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_custom_font_title)).setSelectedValue(Preferences.customFont)
(0..1).forEach {
dialog.addItem(getString(SettingsStringHelper.getCustomFontLabel(it)), it)
}
dialog.addOnSelectItemListener { value ->
Preferences.customFont = value
}.show()
/*
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "* / *" TO FIX WITHOUT SPACE
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(Intent.createChooser(intent, "Select a File to Upload"), Constants.CUSTOM_FONT_CHOOSER_REQUEST_CODE)
} catch (ex: android.content.ActivityNotFoundException) {
Toast.makeText(this, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
}
*/
}
action_show_dividers.setOnClickListener {
show_dividers_toggle.isChecked = !show_dividers_toggle.isChecked
}
show_dividers_toggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showDividers = isChecked
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code -> {
/*val uri = data.data
Log.d("AW", "File Uri: " + uri.toString())
val path = Util.getPath(this, uri)
Log.d("AW", "File Path: " + path)
SP.edit()
.putString(Constants.PREF_CUSTOM_FONT_FILE, path)
.commit()
sendBroadcast(Intent(Constants.ACTION_TIME_UPDATE))
updateSettings()*/
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
}

View File

@ -0,0 +1,397 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.CustomNotesDialog
import com.tommasoberlose.anotherwidget.components.GlanceProviderSortMenu
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentGlanceSettingsBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver.Companion.FITNESS_OPTIONS
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.checkIfFitInstalled
import com.tommasoberlose.anotherwidget.utils.toast
import kotlinx.android.synthetic.main.fragment_calendar_settings.*
import kotlinx.android.synthetic.main.fragment_glance_settings.*
import kotlinx.android.synthetic.main.fragment_glance_settings.scrollView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class GlanceTabFragment : Fragment() {
companion object {
fun newInstance() = GlanceTabFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentGlanceSettingsBinding>(inflater, R.layout.fragment_glance_settings, container, false)
subscribeUi(binding, viewModel)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
action_show_steps.isVisible = requireContext().checkIfFitInstalled()
setupListener()
updateNextAlarmWarningUi()
}
private fun subscribeUi(
binding: FragmentGlanceSettingsBinding,
viewModel: MainViewModel
) {
binding.isGlanceVisible = Preferences.showGlance
viewModel.showGlance.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
binding.isGlanceVisible = it
show_glance_label.text = if (it) getString(R.string.description_show_glance_visible) else getString(R.string.description_show_glance_not_visible)
}
})
viewModel.showMusic.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
checkNotificationPermission()
}
})
viewModel.showNextAlarm.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
updateNextAlarmWarningUi()
}
})
viewModel.showBatteryCharging.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_low_battery_level_warning_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
})
viewModel.showDailySteps.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_steps_label?.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
checkFitnessPermission()
})
viewModel.customInfo.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_custom_notes_label?.text = if (it == "") getString(R.string.settings_not_visible) else it
}
})
}
private fun setupListener() {
action_show_glance.setOnClickListener {
Preferences.showGlance = !Preferences.showGlance
}
show_glance_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showGlance = enabled
}
action_sort_glance_providers.setOnClickListener {
GlanceProviderSortMenu(requireContext())
.show()
}
action_show_music.setOnClickListener {
if (Preferences.showGlance) {
BottomSheetMenu<Boolean>(
requireContext(),
header = getString(R.string.settings_show_music_title)
).setSelectedValue(Preferences.showMusic)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showMusic = value
}.show()
}
}
action_show_next_alarm.setOnClickListener {
if (Preferences.showGlance) {
BottomSheetMenu<Boolean>(
requireContext(),
header = getString(R.string.settings_show_next_alarm_title)
).setSelectedValue(Preferences.showNextAlarm)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showNextAlarm = value
}.show()
}
}
action_show_next_alarm.setOnLongClickListener {
with(requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) {
val pm = requireContext().packageManager as PackageManager
val appNameOrPackage = try {
pm.getApplicationLabel(pm.getApplicationInfo(alarm.showIntent?.creatorPackage ?: "", 0))
} catch (e: Exception) {
alarm.showIntent?.creatorPackage ?: ""
}
MaterialBottomSheetDialog(requireContext(), message = getString(R.string.next_alarm_warning).format(appNameOrPackage))
.setPositiveButton(getString(android.R.string.ok))
.show()
}
}
true
}
action_show_low_battery_level_warning.setOnClickListener {
if (Preferences.showGlance) {
BottomSheetMenu<Boolean>(
requireContext(),
header = getString(R.string.settings_low_battery_level_title)
).setSelectedValue(Preferences.showBatteryCharging)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
Preferences.showBatteryCharging = value
}.show()
}
}
action_show_steps.setOnClickListener {
if (Preferences.showGlance) {
BottomSheetMenu<Boolean>(
requireContext(),
header = getString(R.string.settings_daily_steps_title)
).setSelectedValue(Preferences.showDailySteps)
.addItem(getString(R.string.settings_visible), true)
.addItem(getString(R.string.settings_not_visible), false)
.addOnSelectItemListener { value ->
if (value) {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(requireContext())
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
val mGoogleSignInClient = GoogleSignIn.getClient(requireActivity(), GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(FITNESS_OPTIONS).build())
startActivityForResult(mGoogleSignInClient.signInIntent, 2)
} else {
Preferences.showDailySteps = true
}
} else {
Preferences.showDailySteps = false
}
}.show()
}
}
action_show_custom_notes.setOnClickListener {
if (Preferences.showGlance) {
CustomNotesDialog(requireContext()).show()
}
}
}
private fun updateNextAlarmWarningUi() {
with(requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (AlarmHelper.isAlarmProbablyWrong(requireContext()) && alarm != null && alarm.showIntent != null) {
val pm = requireContext().packageManager as PackageManager
val appNameOrPackage = try {
pm.getApplicationLabel(pm.getApplicationInfo(alarm.showIntent?.creatorPackage ?: "", 0))
} catch (e: Exception) {
alarm.showIntent?.creatorPackage ?: ""
}
show_next_alarm_warning.text = getString(R.string.next_alarm_warning).format(appNameOrPackage)
} else {
show_next_alarm_label?.text = if (Preferences.showNextAlarm) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible)
}
}
}
private val nextAlarmChangeBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateNextAlarmWarningUi()
}
}
override fun onStart() {
super.onStart()
activity?.registerReceiver(nextAlarmChangeBroadcastReceiver, IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED))
}
override fun onStop() {
activity?.unregisterReceiver(nextAlarmChangeBroadcastReceiver)
super.onStop()
}
private fun checkNotificationPermission() {
when {
NotificationManagerCompat.getEnabledListenerPackages(requireContext()).contains(requireContext().packageName) -> {
notification_permission_alert?.isVisible = false
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
show_music_label?.text = if (Preferences.showMusic) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
}
Preferences.showMusic -> {
notification_permission_alert?.isVisible = true
show_music_label?.text = getString(R.string.settings_request_notification_access)
notification_permission_alert?.setOnClickListener {
activity?.startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
}
else -> {
show_music_label?.text = getString(R.string.settings_not_visible)
notification_permission_alert?.isVisible = false
}
}
}
private fun checkFitnessPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || activity?.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION) == true) {
fitness_permission_alert?.isVisible = false
if (Preferences.showDailySteps) {
ActivityDetectionReceiver.registerFence(requireContext())
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
}
show_steps_label?.text = if (Preferences.showDailySteps) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(requireContext())
fitness_permission_alert?.isVisible = true
show_steps_label?.text = getString(R.string.settings_request_fitness_access)
fitness_permission_alert?.setOnClickListener {
requireFitnessPermission()
}
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
show_steps_label?.text = getString(R.string.settings_not_visible)
fitness_permission_alert?.isVisible = false
}
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
when (requestCode) {
1 -> {
if (resultCode == Activity.RESULT_OK) {
checkFitnessPermission()
} else {
Preferences.showDailySteps = false
}
}
2-> {
try {
val account: GoogleSignInAccount? = GoogleSignIn.getSignedInAccountFromIntent(data).getResult(ApiException::class.java)
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
GoogleSignIn.requestPermissions(
requireActivity(),
1,
account,
FITNESS_OPTIONS)
} else {
checkFitnessPermission()
}
} catch (e: ApiException) {
e.printStackTrace()
Preferences.showDailySteps = false
}
}
}
}
private fun requireFitnessPermission() {
Dexter.withContext(requireContext())
.withPermissions(
"com.google.android.gms.permission.ACTIVITY_RECOGNITION",
"android.gms.permission.ACTIVITY_RECOGNITION",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else "com.google.android.gms.permission.ACTIVITY_RECOGNITION"
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
checkFitnessPermission()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
}
})
.check()
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.isScrollable = true
}
}
override fun onResume() {
super.onResume()
checkNotificationPermission()
}
}

View File

@ -0,0 +1,401 @@
package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.animation.ValueAnimator
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.core.animation.addListener
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.BitmapHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.adapters.ViewPagerAdapter
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.getCurrentWallpaper
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlinx.android.synthetic.main.fragment_app_main.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener {
companion object {
fun newInstance() = MainFragment()
private const val PREVIEW_BASE_HEIGHT = 120
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
return inflater.inflate(R.layout.fragment_app_main, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUi(viewModel)
// Viewpager
pager.adapter = ViewPagerAdapter(requireActivity())
pager.offscreenPageLimit = 4
TabLayoutMediator(tabs, pager) { tab, position ->
tab.text = when (position) {
0 -> getString(R.string.settings_general_title)
1 -> getString(R.string.settings_calendar_title)
2 -> getString(R.string.settings_weather_title)
3 -> getString(R.string.settings_clock_title)
4 -> getString(R.string.settings_at_a_glance_title)
else -> ""
}
}.attach()
// Init clock
time.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(requireContext()))
time_am_pm.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2)
time_container.isVisible = Preferences.showClock
preview.layoutParams = preview.layoutParams.apply {
height = PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(requireContext()) else 0
}
subscribeUi(viewModel)
updateUI()
// Warnings
if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) {
MaterialBottomSheetDialog(requireContext(), getString(R.string.xiaomi_warning_title), getString(R.string.xiaomi_warning_message))
.setNegativeButton(getString(R.string.action_ignore)) {
Preferences.showXiaomiWarning = false
}
.setPositiveButton(getString(R.string.action_grant_permission)) {
Preferences.showXiaomiWarning = false
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${activity?.packageName}")
}
startActivity(intent)
}
.show()
}
}
private var uiJob: Job? = null
private fun updateUI() {
uiJob?.cancel()
preview?.clearAnimation()
time_container?.clearAnimation()
if (Preferences.showPreview) {
preview?.setCardBackgroundColor(
ContextCompat.getColor(
requireContext(),
if (ColorHelper.getFontColor(activity?.isDarkTheme() == true)
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
)
widget_shape_background?.setImageDrawable(
BitmapHelper.getTintedDrawable(
requireContext(),
R.drawable.card_background,
ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true)
)
)
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.generateWidgetView(requireContext())
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview?.measure(0, 0)
}
val bitmap = if (preview != null) {
BitmapHelper.getBitmapFromView(
generatedView,
if (preview.width > 0) preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
} else {
null
}
withContext(Dispatchers.Main) {
// Clock
time?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
time_am_pm?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
time_am_pm?.isVisible = Preferences.showAMPMIndicator
// Clock bottom margin
clock_bottom_margin_none?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.value
clock_bottom_margin_small?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value
clock_bottom_margin_medium?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value
clock_bottom_margin_large?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value
if ((Preferences.showClock && (time?.alpha ?: 1f < 1f)) || (!Preferences.showClock && (time?.alpha ?: 0f > 0f))) {
if (Preferences.showClock) {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
val initialHeight = time_container?.measuredHeight ?: 0
ValueAnimator.ofFloat(
if (Preferences.showClock) 0f else 1f,
if (Preferences.showClock) 1f else 0f
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
time_container?.layoutParams =
time_container.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
time?.alpha = animatedValue
}
addListener(
onStart = {
if (Preferences.showClock) {
time_container?.isVisible = true
}
},
onEnd = {
if (!Preferences.showClock) {
time_container?.isVisible = false
}
}
)
}.start()
if (preview != null) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 500L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}
}.start()
}
} else {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
if (preview != null && preview.height == 0) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview?.layoutParams = layoutParams
}
}
}.start()
}
widget_loader?.animate()?.scaleX(0f)?.scaleY(0f)?.alpha(0f)?.setDuration(200L)?.start()
bitmap_container?.apply {
setImageBitmap(bitmap)
scaleX = 0.9f
scaleY = 0.9f
}
widget?.animate()?.alpha(1f)?.start()
}
}
} else {
if (preview != null) {
ValueAnimator.ofInt(
preview.height,
0
).apply {
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}
}.start()
}
}
showErrorBadge()
}
private fun subscribeUi(viewModel: MainViewModel) {
viewModel.showWallpaper.observe(viewLifecycleOwner, Observer {
activity?.let { act ->
val wallpaper = act.getCurrentWallpaper()
widget_bg.setImageDrawable(if (it) wallpaper else null)
if (wallpaper != null) {
widget_bg.layoutParams =
(widget_bg.layoutParams as ViewGroup.MarginLayoutParams).apply {
val metrics = DisplayMetrics()
act.windowManager.defaultDisplay.getMetrics(metrics)
val dimensions: Pair<Int, Int> = if (wallpaper.intrinsicWidth >= wallpaper.intrinsicHeight) {
metrics.heightPixels to (wallpaper.intrinsicWidth) * metrics.heightPixels / (wallpaper.intrinsicHeight)
} else {
metrics.widthPixels to (wallpaper.intrinsicHeight) * metrics.widthPixels / (wallpaper.intrinsicWidth)
}
setMargins(
if (dimensions.first >= dimensions.second) (-80).toPixel(requireContext()) else 0,
(-80).toPixel(requireContext()), 0, 0
)
width = dimensions.first
height = dimensions.second
}
}
}
})
logo.setOnClickListener {
// startActivity(Intent(this, SupportDevActivity::class.java))
}
action_settings.setOnClickListener {
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment)
}
}
private fun showErrorBadge() {
// Calendar error indicator
tabs?.getTabAt(1)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showEvents && activity?.checkGrantedPermission(Manifest.permission.READ_CALENDAR) != true
// Weather error indicator
tabs?.getTabAt(2)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showWeather && (Preferences.weatherProviderApi == "" || (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) != true))
// Music error indicator
tabs?.getTabAt(4)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showMusic && !NotificationManagerCompat.getEnabledListenerPackages(requireContext()).contains(requireContext().packageName)
}
override fun onResume() {
super.onResume()
Preferences.preferences.registerOnSharedPreferenceChangeListener(this)
EventBus.getDefault().register(this)
showErrorBadge()
updateUI()
}
override fun onPause() {
Preferences.preferences.unregisterOnSharedPreferenceChangeListener(this)
EventBus.getDefault().unregister(this)
super.onPause()
}
private var delayJob: Job? = null
override fun onSharedPreferenceChanged(preferences: SharedPreferences, p1: String) {
delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
withContext(Dispatchers.Main) {
updateUI()
}
}
MainWidget.updateWidget(requireContext())
}
class UpdateUiMessageEvent
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: UpdateUiMessageEvent?) {
delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(200)
withContext(Dispatchers.Main) {
updateUI()
}
}
}
}

View File

@ -2,7 +2,6 @@ package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
@ -15,6 +14,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
@ -23,30 +24,34 @@ import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.BuildConfig
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentAdvancedSettingsBinding
import com.tommasoberlose.anotherwidget.databinding.FragmentSettingsBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.SupportDevActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.activities.IntegrationsActivity
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.openURI
import kotlinx.android.synthetic.main.fragment_advanced_settings.*
import kotlinx.android.synthetic.main.fragment_settings.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class AdvancedSettingsFragment : Fragment() {
class SettingsFragment : Fragment() {
companion object {
fun newInstance() = AdvancedSettingsFragment()
fun newInstance() = SettingsFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
@ -55,23 +60,29 @@ class AdvancedSettingsFragment : Fragment() {
): View {
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
val binding = DataBindingUtil.inflate<FragmentAdvancedSettingsBinding>(inflater, R.layout.fragment_advanced_settings, container, false)
subscribeUi(viewModel)
val binding = DataBindingUtil.inflate<FragmentSettingsBinding>(inflater, R.layout.fragment_settings, container, false)
binding.lifecycleOwner = this
binding.viewModel = viewModel
subscribeUi(viewModel)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
action_back.setOnClickListener {
Navigation.findNavController(it).popBackStack()
}
show_widget_preview_toggle.isChecked = Preferences.showPreview
show_wallpaper_toggle.isChecked = Preferences.showWallpaper
setupListener()
app_version.text = "v%s (%s)".format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
requirePermission()
}
private fun subscribeUi(
@ -90,6 +101,10 @@ class AdvancedSettingsFragment : Fragment() {
}
})
viewModel.installedIntegrations.observe(viewLifecycleOwner, Observer {
integrations_count_label?.text = getString(R.string.label_count_installed_integrations).format(it)
})
viewModel.showPreview.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_widget_preview_label?.text =
@ -108,6 +123,33 @@ class AdvancedSettingsFragment : Fragment() {
}
private fun setupListener() {
action_show_widget_preview.setOnClickListener {
show_widget_preview_toggle.isChecked = !show_widget_preview_toggle.isChecked
}
show_widget_preview_toggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showPreview = isChecked
}
action_show_wallpaper.setOnClickListener {
}
action_show_wallpaper.setOnClickListener {
show_wallpaper_toggle.isChecked = !show_wallpaper_toggle.isChecked
}
show_wallpaper_toggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
requirePermission()
} else {
Preferences.showWallpaper = isChecked
}
}
action_integrations.setOnClickListener {
startActivity(Intent(requireContext(), IntegrationsActivity::class.java))
}
action_change_theme.setOnClickListener {
maintainScrollPosition {
BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_theme_title))
@ -130,46 +172,6 @@ class AdvancedSettingsFragment : Fragment() {
}
}
action_show_widget_preview.setOnClickListener {
maintainScrollPosition {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.action_show_widget_preview))
.setSelectedValue(Preferences.showPreview)
.addItem(
getString(R.string.settings_visible),
true
)
.addItem(
getString(R.string.settings_not_visible),
false
)
.addOnSelectItemListener { value ->
Preferences.showPreview = value
}.show()
}
}
action_show_wallpaper.setOnClickListener {
maintainScrollPosition {
BottomSheetMenu<Boolean>(requireContext(), header = getString(R.string.settings_title_show_wallpaper))
.setSelectedValue(Preferences.showWallpaper && activity?.checkGrantedPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == true)
.addItem(
getString(R.string.settings_visible),
true
)
.addItem(
getString(R.string.settings_not_visible),
false
)
.addOnSelectItemListener { value ->
if (value) {
requirePermission()
} else {
Preferences.showWallpaper = value
}
}.show()
}
}
action_translate.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/app/src/main/res/values/strings.xml")
}
@ -187,17 +189,18 @@ class AdvancedSettingsFragment : Fragment() {
}
action_refresh_widget.setOnClickListener {
MainWidget.updateWidget(requireContext())
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
}
}
private fun maintainScrollPosition(callback: () -> Unit) {
val scrollPosition = scrollView.scrollY
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.smoothScrollTo(0, scrollPosition)
scrollView.isScrollable = true
}
}
@ -208,8 +211,11 @@ class AdvancedSettingsFragment : Fragment() {
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
Preferences.showWallpaper = false
Preferences.showWallpaper = report.areAllPermissionsGranted()
if (report.areAllPermissionsGranted()) {
Preferences.showWallpaper = true
} else {
show_wallpaper_toggle?.isChecked = false
}
}
}
override fun onPermissionRationaleShouldBeShown(

View File

@ -5,10 +5,12 @@ import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.os.BuildCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
@ -23,13 +25,16 @@ import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.IconPackSelector
import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentWeatherSettingsBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.global.RequestCode
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomLocationActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
@ -42,10 +47,10 @@ import kotlinx.android.synthetic.main.fragment_weather_settings.scrollView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class WeatherSettingsFragment : Fragment() {
class WeatherTabFragment : Fragment() {
companion object {
fun newInstance() = WeatherSettingsFragment()
fun newInstance() = WeatherTabFragment()
}
private lateinit var viewModel: MainViewModel
@ -80,14 +85,18 @@ class WeatherSettingsFragment : Fragment() {
binding: FragmentWeatherSettingsBinding,
viewModel: MainViewModel
) {
binding.isWeatherVisible = Preferences.showWeather
viewModel.showWeatherWarning.observe(viewLifecycleOwner, Observer {
weather_warning?.isVisible = it
checkLocationPermission()
})
viewModel.showWeather.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
show_weather_label?.text =
if (it) getString(R.string.show_weather_visible) else getString(R.string.show_weather_not_visible)
checkWeatherProviderConfig()
binding.isWeatherVisible = it
}
checkLocationPermission()
@ -95,17 +104,14 @@ class WeatherSettingsFragment : Fragment() {
viewModel.weatherProviderApi.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_provider_api_key?.text =
if (it == "") getString(R.string.settings_weather_provider_api_key_subtitle_not_set) else getString(
R.string.settings_weather_provider_api_key_subtitle_all_set
)
label_weather_provider_api_key?.setTextColor(ContextCompat.getColor(requireContext(), if (it == "") R.color.errorColorText else R.color.colorSecondaryText))
checkWeatherProviderConfig()
}
checkLocationPermission()
})
viewModel.customLocationAdd.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
background_location_warning.isVisible = it == ""
label_custom_location?.text =
if (it == "") getString(R.string.custom_location_gps) else it
}
@ -127,6 +133,19 @@ class WeatherSettingsFragment : Fragment() {
checkLocationPermission()
})
viewModel.weatherIconPack.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_icon_pack?.text = getString(R.string.settings_weather_icon_pack_default).format((it + 1))
// weather_icon_pack.setImageDrawable(ContextCompat.getDrawable(requireContext(), WeatherHelper.getWeatherIconResource("02d")))
// if (it == Constants.WeatherIconPack.MINIMAL.value) {
// weather_icon_pack.setColorFilter(ContextCompat.getColor(requireContext(), R.color.colorPrimaryText))
// } else {
// weather_icon_pack.setColorFilter(ContextCompat.getColor(requireContext(), android.R.color.transparent))
// }
}
checkLocationPermission()
})
viewModel.weatherAppName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
weather_app_label?.text =
@ -137,21 +156,35 @@ class WeatherSettingsFragment : Fragment() {
private fun checkLocationPermission() {
// Background permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && activity?.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) == true) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && activity?.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) == true && activity?.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != true) {
requirePermission()
}
if (activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) == true) {
location_permission_alert?.isVisible = false
WeatherWorker.setUpdates(requireContext())
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
location_permission_alert?.isVisible = true
location_permission_alert?.setOnClickListener {
requirePermission()
MaterialBottomSheetDialog(requireContext(), message = getString(R.string.background_location_warning))
.setPositiveButton(getString(android.R.string.ok)) {
requirePermission()
}
.show()
}
} else {
location_permission_alert?.isVisible = false
}
}
private fun checkWeatherProviderConfig() {
label_weather_provider_api_key?.text =
if (Preferences.weatherProviderApi == "") getString(R.string.settings_weather_provider_api_key_subtitle_not_set) else getString(
R.string.settings_weather_provider_api_key_subtitle_all_set
)
label_weather_provider_api_key?.setTextColor(ContextCompat.getColor(requireContext(), if (Preferences.weatherProviderApi == "" && Preferences.showWeather) R.color.errorColorText else R.color.colorSecondaryText))
}
private fun setupListener() {
action_hide_weather_warning.setOnClickListener {
Preferences.showWeatherWarning = false
@ -161,6 +194,10 @@ class WeatherSettingsFragment : Fragment() {
Preferences.showWeather = !Preferences.showWeather
}
show_weather_switch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showWeather = enabled
}
action_weather_provider_api_key.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
@ -185,6 +222,9 @@ class WeatherSettingsFragment : Fragment() {
.addItem(getString(R.string.fahrenheit), "F")
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
WeatherHelper.updateWeather(requireContext())
}
Preferences.weatherTempUnit = value
}.show()
}
@ -204,6 +244,12 @@ class WeatherSettingsFragment : Fragment() {
}
}
action_weather_icon_pack.setOnClickListener {
if (Preferences.showWeather) {
IconPackSelector(requireContext(), header = getString(R.string.settings_weather_icon_pack_title)).show()
}
}
action_weather_app.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
@ -218,7 +264,7 @@ class WeatherSettingsFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
WeatherWorker.setUpdates(requireContext())
WeatherReceiver.setUpdates(requireContext())
checkLocationPermission()
}
RequestCode.WEATHER_APP_REQUEST_CODE.code -> {
@ -229,7 +275,7 @@ class WeatherSettingsFragment : Fragment() {
MainWidget.updateWidget(requireContext())
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
WeatherWorker.setOneTimeUpdate(requireContext())
WeatherReceiver.setOneTimeUpdate(requireContext())
}
}
}
@ -261,11 +307,11 @@ class WeatherSettingsFragment : Fragment() {
}
private fun maintainScrollPosition(callback: () -> Unit) {
val scrollPosition = scrollView.scrollY
scrollView.isScrollable = false
callback.invoke()
lifecycleScope.launch {
delay(200)
scrollView.smoothScrollTo(0, scrollPosition)
scrollView.isScrollable = true
}
}
}

View File

@ -3,8 +3,11 @@ package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.chibatching.kotpref.livedata.asLiveData
import com.tommasoberlose.anotherwidget.global.Preferences
class CustomDateViewModel(application: Application) : AndroidViewModel(application) {
val dateInput: MutableLiveData<String> = MutableLiveData(if (Preferences.dateFormat == "") "EEEE, MMM dd" else Preferences.dateFormat)
val isDateCapitalize = MutableLiveData<Boolean>(Preferences.isDateCapitalize)
val isDateUppercase = MutableLiveData<Boolean>(Preferences.isDateUppercase)
}

View File

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

View File

@ -9,13 +9,24 @@ class MainViewModel : ViewModel() {
// General Settings
val textGlobalColor = Preferences.asLiveData(Preferences::textGlobalColor)
val textGlobalAlpha = Preferences.asLiveData(Preferences::textGlobalAlpha)
val textSecondaryColor = Preferences.asLiveData(Preferences::textSecondaryColor)
val textSecondaryAlpha = Preferences.asLiveData(Preferences::textSecondaryAlpha)
val backgroundCardColor = Preferences.asLiveData(Preferences::backgroundCardColor)
val backgroundCardAlpha = Preferences.asLiveData(Preferences::backgroundCardAlpha)
val textGlobalColorDark = Preferences.asLiveData(Preferences::textGlobalColorDark)
val textGlobalAlphaDark = Preferences.asLiveData(Preferences::textGlobalAlphaDark)
val textSecondaryColorDark = Preferences.asLiveData(Preferences::textSecondaryColorDark)
val textSecondaryAlphaDark = Preferences.asLiveData(Preferences::textSecondaryAlphaDark)
val backgroundCardColorDark = Preferences.asLiveData(Preferences::backgroundCardColorDark)
val backgroundCardAlphaDark = Preferences.asLiveData(Preferences::backgroundCardAlphaDark)
val textMainSize = Preferences.asLiveData(Preferences::textMainSize)
val textSecondSize = Preferences.asLiveData(Preferences::textSecondSize)
val textShadow = Preferences.asLiveData(Preferences::textShadow)
val textShadowDark = Preferences.asLiveData(Preferences::textShadowDark)
val customFont = Preferences.asLiveData(Preferences::customFont)
val secondRowInformation = Preferences.asLiveData(Preferences::secondRowInformation)
val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
// Calendar Settings
val showEvents = Preferences.asLiveData(Preferences::showEvents)
@ -26,14 +37,19 @@ class MainViewModel : ViewModel() {
val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent)
val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails)
val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName)
val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency)
val showOnlyBusyEvents = Preferences.asLiveData(Preferences::showOnlyBusyEvents)
// Clock Settings
val showClock = Preferences.asLiveData(Preferences::showClock)
val clockTextSize = Preferences.asLiveData(Preferences::clockTextSize)
val clockTextColor = Preferences.asLiveData(Preferences::clockTextColor)
val clockTextAlpha = Preferences.asLiveData(Preferences::clockTextAlpha)
val clockTextColorDark = Preferences.asLiveData(Preferences::clockTextColorDark)
val clockTextAlphaDark = Preferences.asLiveData(Preferences::clockTextAlphaDark)
val showAMPMIndicator = Preferences.asLiveData(Preferences::showAMPMIndicator)
val clockAppName = Preferences.asLiveData(Preferences::clockAppName)
val showNextAlarm = Preferences.asLiveData(Preferences::showNextAlarm)
val dateFormat = Preferences.asLiveData(Preferences::dateFormat)
val clockBottomMargin = Preferences.asLiveData(Preferences::clockBottomMargin)
@ -50,9 +66,19 @@ class MainViewModel : ViewModel() {
val customLocationAdd = Preferences.asLiveData(Preferences::customLocationAdd)
val showWeatherWarning = Preferences.asLiveData(Preferences::showWeatherWarning)
val weatherIconPack = Preferences.asLiveData(Preferences::weatherIconPack)
// Glance
val showGlance = Preferences.asLiveData(Preferences::showGlance)
val showMusic = Preferences.asLiveData(Preferences::showMusic)
val showNextAlarm = Preferences.asLiveData(Preferences::showNextAlarm)
val showBatteryCharging = Preferences.asLiveData(Preferences::showBatteryCharging)
val showDailySteps = Preferences.asLiveData(Preferences::showDailySteps)
val customInfo = Preferences.asLiveData(Preferences::customNotes)
// Advanced Settings
val darkThemePreference = Preferences.asLiveData(Preferences::darkThemePreference)
val showWallpaper = Preferences.asLiveData(Preferences::showWallpaper)
val showPreview = Preferences.asLiveData(Preferences::showPreview)
val installedIntegrations = Preferences.asLiveData(Preferences::installedIntegrations)
}

View File

@ -15,32 +15,28 @@ import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.WidgetHelper.reduceDimensionWithMaxWidth
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.receivers.*
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlinx.android.synthetic.main.the_widget.view.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import java.lang.Exception
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.min
import kotlin.math.roundToInt
class MainWidget : AppWidgetProvider() {
@ -58,7 +54,8 @@ class MainWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) {
CalendarHelper.updateEventList(context)
WeatherWorker.setUpdates(context)
WeatherReceiver.setUpdates(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
if (Preferences.showEvents) {
CalendarHelper.setEventUpdatesAndroidN(context)
@ -70,20 +67,14 @@ class MainWidget : AppWidgetProvider() {
override fun onDisabled(context: Context) {
if (getWidgetCount(context) == 0) {
UpdatesReceiver.removeUpdates(context)
WeatherWorker.removeUpdates(context)
WeatherReceiver.removeUpdates(context)
}
}
companion object {
fun updateWidget(context: Context) {
val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java)
val widgetIds = widgetManager.getAppWidgetIds(widgetComponent)
val update = Intent(context, MainWidget::class.java)
update.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds)
update.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
context.sendBroadcast(update)
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
}
fun getWidgetCount(context: Context): Int {
@ -96,35 +87,62 @@ class MainWidget : AppWidgetProvider() {
appWidgetId: Int) {
val displayMetrics = Resources.getSystem().displayMetrics
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId)
generateWidgetView(context, appWidgetId, appWidgetManager, dimensions.first - 8.toPixel(context) /*width - 16.toPixel(context)*/)
generateWidgetView(context, appWidgetId, appWidgetManager, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)))
}
private fun generateWidgetView(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager, w: Int) {
var views = RemoteViews(context.packageName, R.layout.the_widget_sans)
val generatedView = generateWidgetView(context)
views.setImageViewBitmap(R.id.bitmap_container, BitmapHelper.getBitmapFromView(generatedView, width = w))
// Background
views.setInt(R.id.widget_shape_background, "setColorFilter", ColorHelper.getBackgroundColorRgb())
views.setInt(R.id.widget_shape_background, "setImageAlpha", ColorHelper.getBackgroundAlpha())
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
views = updateClockView(context, views, appWidgetId)
// Setup listener
views = updateCalendarView(context, generatedView, views, appWidgetId)
views = updateWeatherView(context, generatedView, views, appWidgetId)
try {
val generatedView = generateWidgetView(context)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedView, width = w)
)
views = updateCalendarView(context, generatedView, views, appWidgetId)
views = updateWeatherView(context, generatedView, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
private fun updateCalendarView(context: Context, v: View, views: RemoteViews, widgetID: Int): RemoteViews {
val eventRepository = EventRepository(context)
try {
val eventRepository = EventRepository(context)
views.setImageViewBitmap(
R.id.empty_date_rect,
BitmapHelper.getBitmapFromView(v.empty_date, draw = false)
@ -133,12 +151,13 @@ class MainWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.empty_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.second_row_rect, View.GONE)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
0
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.empty_date_rect, calPIntent)
@ -161,7 +180,7 @@ class MainWidget : AppWidgetProvider() {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
0
PendingIntent.FLAG_UPDATE_CURRENT
)
)
@ -179,7 +198,7 @@ class MainWidget : AppWidgetProvider() {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT },
0
PendingIntent.FLAG_UPDATE_CURRENT
)
)
} else {
@ -191,7 +210,7 @@ class MainWidget : AppWidgetProvider() {
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
0
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, pIntent)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, pIntent)
@ -214,7 +233,7 @@ class MainWidget : AppWidgetProvider() {
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
0
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, mapIntent)
} else {
@ -226,7 +245,7 @@ class MainWidget : AppWidgetProvider() {
nextEvent,
forceEventDetails = true
),
0
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, pIntentDetail)
}
@ -244,32 +263,123 @@ class MainWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.empty_layout_rect, View.GONE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
} else if (Preferences.showNextAlarm && nextAlarm != "") {
val clockIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
0
)
views.setOnClickPendingIntent(R.id.second_row_rect, clockIntent)
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(v.next_event, draw = false)
views.setViewVisibility(
R.id.second_row_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
)
views.setImageViewBitmap(
R.id.second_row_rect,
BitmapHelper.getBitmapFromView(v.second_row, draw = false)
views.setViewVisibility(
R.id.second_row_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(R.id.second_row_rect, View.VISIBLE)
views.setViewVisibility(
R.id.second_row_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
)
} else if (GlanceProviderHelper.showGlanceProviders(context) && v.calendar_layout.isVisible) {
var showSomething = false
loop@ for (provider:Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, musicIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
val alarmIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, alarmIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, batteryIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.second_row_rect, fitIntent)
showSomething = true
break@loop
}
}
}
}
views.setViewVisibility(R.id.empty_layout_rect, View.GONE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
if (showSomething) {
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(v.next_event, draw = false)
)
views.setImageViewBitmap(
R.id.second_row_rect,
BitmapHelper.getBitmapFromView(v.second_row, draw = false)
)
views.setViewVisibility(R.id.second_row_rect, View.VISIBLE)
views.setViewVisibility(R.id.empty_layout_rect, View.GONE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setOnClickPendingIntent(R.id.next_event_rect, calPIntent)
views.setViewVisibility(
R.id.second_row_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.second_row_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
)
}
}
} catch (ex: Exception) {
ex.printStackTrace()
FirebaseCrashlytics.getInstance().recordException(ex)
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
@ -280,6 +390,7 @@ class MainWidget : AppWidgetProvider() {
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.special_weather_rect, View.VISIBLE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
@ -287,6 +398,7 @@ class MainWidget : AppWidgetProvider() {
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.calendar_weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.special_weather_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
@ -297,13 +409,25 @@ class MainWidget : AppWidgetProvider() {
R.id.calendar_weather_rect,
BitmapHelper.getBitmapFromView(v.calendar_weather, draw = false)
)
views.setImageViewBitmap(
R.id.special_weather_rect,
BitmapHelper.getBitmapFromView(v.calendar_weather, draw = false)
)
if (GlanceProviderHelper.showGlanceProviders(context)) {
views.setViewVisibility(R.id.calendar_weather_rect, View.GONE)
} else {
views.setViewVisibility(R.id.special_weather_rect, View.GONE)
}
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.calendar_weather_rect, View.GONE)
views.setViewVisibility(R.id.special_weather_rect, View.GONE)
}
} catch (ex: Exception) {
ex.printStackTrace()
FirebaseCrashlytics.getInstance().recordException(ex)
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
@ -318,8 +442,8 @@ class MainWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.clock_bottom_margin_medium, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_large, View.GONE)
} else {
views.setTextColor(R.id.time, ColorHelper.getFontColor())
views.setTextColor(R.id.time_am_pm, ColorHelper.getFontColor())
views.setTextColor(R.id.time, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.time_am_pm, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextViewTextSize(
R.id.time,
TypedValue.COMPLEX_UNIT_SP,
@ -339,7 +463,7 @@ class MainWidget : AppWidgetProvider() {
views.setOnClickPendingIntent(R.id.time, clockPIntent)
views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent)
views.setViewVisibility(R.id.time, View.VISIBLE)
views.setViewVisibility(R.id.time_am_pm, View.VISIBLE)
views.setViewVisibility(R.id.time_am_pm, if (Preferences.showAMPMIndicator) View.VISIBLE else View.GONE)
views.setViewVisibility(
R.id.clock_bottom_margin_none,
@ -360,7 +484,7 @@ class MainWidget : AppWidgetProvider() {
}
} catch (ex: Exception) {
ex.printStackTrace()
FirebaseCrashlytics.getInstance().recordException(ex)
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
@ -372,10 +496,14 @@ class MainWidget : AppWidgetProvider() {
val eventRepository = EventRepository(context)
val v = View.inflate(context, R.layout.the_widget, null)
val now = Calendar.getInstance()
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
v.empty_layout.visibility = View.VISIBLE
v.calendar_layout.visibility = View.GONE
v.next_event_difference_time.visibility = View.GONE
v.action_next.isVisible = false
v.action_previous.isVisible = false
@ -391,7 +519,7 @@ class MainWidget : AppWidgetProvider() {
v.next_event.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < (nextEvent.startDate - 1000 * 60 * 60)) {
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
v.next_event_difference_time.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
@ -413,8 +541,8 @@ class MainWidget : AppWidgetProvider() {
} else {
v.second_row_icon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.round_today))
if (!nextEvent.allDay) {
val startHour = DateFormat.getTimeInstance(DateFormat.SHORT).format(nextEvent.startDate)
val endHour = DateFormat.getTimeInstance(DateFormat.SHORT).format(nextEvent.endDate)
val startHour = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(nextEvent.startDate)
val endHour = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(nextEvent.endDate)
var dayDiff = TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
@ -437,45 +565,143 @@ class MainWidget : AppWidgetProvider() {
} else {
val flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
v.next_event_date.text = DateUtils.formatDateTime(context, now.timeInMillis, flags).getCapWordString()
v.next_event_date.text = DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
} else if (Preferences.showNextAlarm && nextAlarm != "") {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm
)
)
v.next_event.text = DateHelper.getDateText(context, now)
v.next_event_date.text = AlarmHelper.getNextAlarm(context)
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
v.second_row_icon.isVisible = true
var showSomething = false
loop@ for (provider:Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note
)
)
v.next_event_date.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm
)
)
v.next_event_date.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
v.second_row_icon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel == 100) {
v.next_event_date.text = "%s - %d%%".format(context.getString(R.string.charging), batteryLevel)
} else {
v.next_event_date.text = context.getString(R.string.charging)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
v.second_row_icon.isVisible = false
v.next_event_date.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
v.second_row_icon.isVisible = false
v.next_event_date.text = Preferences.customNotes
v.next_event_date.gravity
v.next_event_date.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
v.second_row_icon.isVisible = false
v.next_event_date.text = context.getString(R.string.daily_steps_counter).format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
v.next_event.text = DateHelper.getDateText(context, now)
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else {
v.second_row_icon.isVisible = false
}
}
// Color
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp).forEach {
it.setTextColor(ColorHelper.getFontColor())
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.divider3, v.special_temp).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
listOf<ImageView>(v.second_row_icon, v.action_next, v.action_previous).forEach {
it.setColorFilter(ColorHelper.getFontColor())
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.value) {
listOf<ImageView>(v.action_next, v.action_previous)
} else {
listOf<ImageView>(v.action_next, v.action_previous, v.empty_weather_icon, v.special_weather_icon)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha = (if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue().toFloat() else Preferences.textGlobalAlpha.toIntValue().toFloat()) / 100
}
listOf<TextView>(v.next_event_date, v.divider2, v.calendar_temp).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.value) {
listOf<ImageView>(v.second_row_icon)
} else {
listOf<ImageView>(v.second_row_icon, v.weather_icon)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha = (if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue().toFloat() else Preferences.textSecondaryAlpha.toIntValue().toFloat()) / 100
}
// Text Size
listOf<Pair<TextView, Float>>(
v.empty_date to Preferences.textMainSize,
v.divider1 to Preferences.textMainSize,
v.divider1 to (Preferences.textMainSize - 2),
v.temp to Preferences.textMainSize,
v.next_event to Preferences.textMainSize,
v.next_event_difference_time to Preferences.textMainSize,
v.next_event_date to Preferences.textSecondSize,
v.divider2 to Preferences.textSecondSize,
v.calendar_temp to Preferences.textSecondSize
v.divider2 to (Preferences.textSecondSize - 2),
v.calendar_temp to Preferences.textSecondSize,
v.divider3 to (Preferences.textMainSize - 2),
v.special_temp to Preferences.textMainSize
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
}
@ -484,11 +710,11 @@ class MainWidget : AppWidgetProvider() {
v.second_row_icon.scaleX = Preferences.textSecondSize / 18f
v.second_row_icon.scaleY = Preferences.textSecondSize / 18f
v.weather_icon.scaleX = Preferences.textSecondSize / 16f
v.weather_icon.scaleY = Preferences.textSecondSize / 16f
v.weather_icon.scaleX = Preferences.textSecondSize / 14f
v.weather_icon.scaleY = Preferences.textSecondSize / 14f
v.empty_weather_icon.scaleX = Preferences.textMainSize / 20f
v.empty_weather_icon.scaleY = Preferences.textMainSize / 20f
v.empty_weather_icon.scaleX = Preferences.textMainSize / 18f
v.empty_weather_icon.scaleY = Preferences.textMainSize / 18f
v.action_next.scaleX = Preferences.textMainSize / 28f
v.action_next.scaleY = Preferences.textMainSize / 28f
@ -496,35 +722,38 @@ class MainWidget : AppWidgetProvider() {
v.action_previous.scaleX = Preferences.textMainSize / 28f
v.action_previous.scaleY = Preferences.textMainSize / 28f
v.special_weather_icon.scaleX = Preferences.textMainSize / 20f
v.special_weather_icon.scaleY = Preferences.textMainSize / 20f
// Shadows
val shadowRadius = when (Preferences.textShadow) {
val shadowRadius = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor = when (Preferences.textShadow) {
val shadowColor = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> R.color.black_50
2 -> Color.BLACK
else -> R.color.black_50
}
val shadowDy = when (Preferences.textShadow) {
val shadowDy = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 1f
else -> 0f
}
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp).forEach {
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp, v.divider3, v.special_temp).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_PRODUCT_SANS) {
val productSans: Typeface = Typeface.createFromAsset(context.assets, "fonts/product_sans_regular.ttf")
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp).forEach {
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp, v.divider3, v.special_temp).forEach {
it.typeface = productSans
}
}
@ -533,26 +762,45 @@ class MainWidget : AppWidgetProvider() {
if (Preferences.showWeather && Preferences.weatherIcon != "") {
v.weather.visibility = View.VISIBLE
v.calendar_weather.visibility = View.VISIBLE
val currentTemp = String.format(Locale.getDefault(), "%.0f °%s", Preferences.weatherTemp, Preferences.weatherRealTempUnit)
v.special_weather.visibility = View.VISIBLE
val currentTemp = String.format(Locale.getDefault(), "%d °%s", Preferences.weatherTemp.roundToInt(), Preferences.weatherRealTempUnit)
val icon: String = Preferences.weatherIcon
if (icon == "") {
v.weather_icon.visibility = View.GONE
v.empty_weather_icon.visibility = View.GONE
v.special_weather_icon.visibility = View.GONE
} else {
v.weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.empty_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.special_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.weather_icon.visibility = View.VISIBLE
v.empty_weather_icon.visibility = View.VISIBLE
v.special_weather_icon.visibility = View.VISIBLE
}
v.temp.text = currentTemp
v.calendar_temp.text = currentTemp
v.special_temp.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
v.calendar_weather.visibility = View.GONE
} else {
v.special_weather.visibility = View.GONE
}
} else {
v.weather.visibility = View.GONE
v.calendar_weather.visibility = View.GONE
v.special_weather.visibility = View.GONE
}
// Dividers
arrayOf(v.divider1, v.divider2, v.divider3).forEach {
it.isVisible = Preferences.showDividers
}
eventRepository.close()
return v
}
}

View File

@ -32,8 +32,8 @@ import java.util.*
fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name)
fun Context.toast(message: String) {
val toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
fun Context.toast(message: String, long: Boolean = false) {
val toast = Toast.makeText(this, message, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)
// toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
@ -143,11 +143,11 @@ fun Context.isTablet(): Boolean {
}
fun String.md5(): String {
val MD5 = "MD5"
val mD5 = "MD5"
try {
// Create MD5 Hash
val digest = java.security.MessageDigest
.getInstance(MD5)
.getInstance(mD5)
digest.update(toByteArray())
val messageDigest = digest.digest()
@ -172,7 +172,7 @@ fun String.isValidEmail(): Boolean
= this.isNotEmpty() &&
Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun Activity.isDarkTheme(): Boolean {
fun Context.isDarkTheme(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
@ -212,4 +212,22 @@ fun String.getCapWordString(): String {
} catch (e: Exception) {
this
}
}
fun Context.checkIfFitInstalled(): Boolean {
return try {
packageManager.getPackageInfo("com.google.android.apps.fitness", PackageManager.GET_ACTIVITIES)
true
} catch (e: Exception) {
false
}
}
fun Intent.isDefaultSet(context: Context): Boolean {
val pm = context.packageManager
return try {
resolveActivity(pm) != null && resolveActivity(pm).packageName.isNotBlank()
} catch (ex: java.lang.Exception) {
false
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

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