Compare commits

...

25 Commits

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

Display the clock in the correct text size.
2021-09-17 12:01:41 +08:00
124 changed files with 1563 additions and 2098 deletions

5
.idea/gradle.xml generated
View File

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

2
.idea/misc.xml generated
View File

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

3
.idea/modules.xml generated
View File

@ -4,6 +4,9 @@
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Another_Widget.iml" filepath="$PROJECT_DIR$/.idea/modules/Another_Widget.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/Another_Widget.iml" filepath="$PROJECT_DIR$/.idea/modules/Another_Widget.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -1,22 +1,11 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
// Apply the Crashlytics Gradle plugin
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
def apikeyPropertiesFile = rootProject.file("apikey.properties")
def apikeyProperties = new Properties()
apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
applicationId "com.tommasoberlose.anotherwidget" applicationId "com.tommasoberlose.anotherwidget"
@ -26,7 +15,6 @@ android {
versionName "2.3.3" versionName "2.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
renderscriptSupportModeEnabled true renderscriptSupportModeEnabled true
} }
@ -70,7 +58,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI // UI
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.3.0' implementation 'androidx.browser:browser:1.3.0'
@ -88,6 +76,10 @@ dependencies {
// EventBus // EventBus
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
// Room
implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
// Navigation // Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
@ -102,23 +94,14 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0'
// Fitness
implementation 'com.google.android.gms:play-services-fitness:20.0.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
//Weather //Weather
implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2' implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2'
implementation 'com.google.android.gms:play-services-location:18.0.0'
// Billing
implementation 'com.android.billingclient:billing:3.0.3'
implementation 'com.android.billingclient:billing-ktx:3.0.3'
// KTX // KTX
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.palette:palette-ktx:1.0.0" implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.5.0'
//Retrofit //Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
@ -130,9 +113,6 @@ dependencies {
//Coroutines //Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
// Preferences // Preferences
implementation 'com.chibatching.kotpref:kotpref:2.13.1' implementation 'com.chibatching.kotpref:kotpref:2.13.1'
implementation 'com.chibatching.kotpref:livedata-support:2.13.1' implementation 'com.chibatching.kotpref:livedata-support:2.13.1'

View File

@ -6,15 +6,15 @@
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/> <uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" /> <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -26,18 +26,17 @@
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity"> tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait"> <activity android:name=".ui.activities.SplashActivity" android:exported="true" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.SupportDevActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.CustomDateActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.settings.IntegrationsActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:screenOrientation="portrait" />
@ -45,11 +44,10 @@
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" /> <activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget"> <receiver android:name=".ui.widgets.MainWidget" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/the_widget_info" /> android:resource="@xml/the_widget_info" />
@ -75,8 +73,8 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_CALENDAR_UPDATE" /> <action android:name="com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_TIME_UPDATE" /> <action android:name="com.tommasoberlose.anotherwidget.action.TIME_UPDATE" />
<action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" /> <action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" /> <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
@ -91,8 +89,7 @@
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_WEATHER_UPDATE" /> <action android:name="com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" /> <action android:name="android.intent.action.TIME_SET" />
@ -104,26 +101,13 @@
<receiver <receiver
android:name=".receivers.WidgetClickListenerReceiver" android:name=".receivers.WidgetClickListenerReceiver"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_OPEN_WEATHER_INTENT" /> <action android:name="com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <service android:name=".receivers.NotificationListener" android:exported="true"
android:name=".receivers.CrashlyticsReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_REPORT_CRASH" />
</intent-filter>
</receiver>
<service android:name=".services.EventListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".services.BatteryListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".receivers.NotificationListener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter> <intent-filter>
<action android:name="android.service.notification.NotificationListenerService" /> <action android:name="android.service.notification.NotificationListenerService" />
@ -141,27 +125,6 @@
<action android:name="android.intent.action.BATTERY_CHANGED"/> <action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter> </intent-filter>
</receiver> </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.UpdateCalendarService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application> </application>
<queries> <queries>

View File

@ -1,35 +1,18 @@
package com.tommasoberlose.anotherwidget package com.tommasoberlose.anotherwidget
import android.Manifest
import android.app.Application import android.app.Application
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.chibatching.kotpref.Kotpref import com.chibatching.kotpref.Kotpref
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import io.realm.Realm
import io.realm.RealmConfiguration
import net.danlew.android.joda.JodaTimeAndroid
class AWApplication : Application() { class AWApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// Firebase crashlitycs
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
// Preferences // Preferences
Kotpref.init(this) Kotpref.init(this)
// Dark theme // Dark theme
AppCompatDelegate.setDefaultNightMode(Preferences.darkThemePreference) AppCompatDelegate.setDefaultNightMode(Preferences.darkThemePreference)
// Realm
Realm.init(this)
val config = RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)
} }
} }

View File

@ -18,7 +18,7 @@ class FixedFocusScrollView @JvmOverloads constructor(
var isScrollable = true var isScrollable = true
override fun scrollTo(x: Int, y: Int) { override fun scrollTo(x: Int, y: Int) {
if (isScrollable) { if (isScrollable || !isLaidOut) {
super.scrollTo(x, y) super.scrollTo(x, y)
} }
} }

View File

@ -6,18 +6,9 @@ import android.app.AlarmManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
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.R
import com.tommasoberlose.anotherwidget.databinding.GlanceProviderSettingsLayoutBinding import com.tommasoberlose.anotherwidget.databinding.GlanceProviderSettingsLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
@ -26,7 +17,6 @@ import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GreetingsHelper import com.tommasoberlose.anotherwidget.helpers.GreetingsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.ui.activities.tabs.AppNotificationsFilterActivity import com.tommasoberlose.anotherwidget.ui.activities.tabs.AppNotificationsFilterActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.MediaInfoFormatActivity import com.tommasoberlose.anotherwidget.ui.activities.tabs.MediaInfoFormatActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.MusicPlayersFilterActivity import com.tommasoberlose.anotherwidget.ui.activities.tabs.MusicPlayersFilterActivity
@ -90,14 +80,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
checkNextAlarm() checkNextAlarm()
} }
/* GOOGLE STEPS */
binding.actionToggleGoogleFit.isVisible = provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS
if (provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS) {
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
/* BATTERY INFO */ /* BATTERY INFO */
if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) { if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) {
binding.warningContainer.isVisible = false binding.warningContainer.isVisible = false
@ -177,6 +159,8 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> { Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked Preferences.showNextAlarm = isChecked
checkNextAlarm() checkNextAlarm()
if (!isChecked)
AlarmHelper.clearTimeout(context)
} }
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> { Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked Preferences.showBatteryCharging = isChecked
@ -184,39 +168,21 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> { Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked Preferences.showNotifications = isChecked
checkLastNotificationsPermission() checkLastNotificationsPermission()
if (!isChecked)
ActiveNotificationsHelper.clearLastNotification(context)
} }
Constants.GlanceProviderId.GREETINGS -> { Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked Preferences.showGreetings = isChecked
GreetingsHelper.toggleGreetings(context) GreetingsHelper.toggleGreetings(context)
} }
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (isChecked) {
val account: GoogleSignInAccount? =
GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS
)
) {
val mGoogleSignInClient =
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS
).build())
context.startActivityForResult(mGoogleSignInClient.signInIntent,
2)
} else {
Preferences.showDailySteps = true
}
} else {
Preferences.showDailySteps = false
}
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
Constants.GlanceProviderId.EVENTS -> { Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked Preferences.showEventsAsGlanceProvider = isChecked
if (isChecked) {
com.tommasoberlose.anotherwidget.db.EventRepository(context).run {
resetNextEventData()
close()
}
}
} }
Constants.GlanceProviderId.WEATHER -> { Constants.GlanceProviderId.WEATHER -> {
Preferences.showWeatherAsGlanceProvider = isChecked Preferences.showWeatherAsGlanceProvider = isChecked
@ -238,8 +204,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
private fun checkNextAlarm() { private fun checkNextAlarm() {
if (!Preferences.showNextAlarm)
AlarmHelper.clearTimeout(context)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) { if (alarm != null && alarm.showIntent != null) {
@ -325,89 +289,4 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
} }
statusCallback?.invoke() statusCallback?.invoke()
} }
private fun checkFitnessPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION)
) {
if (Preferences.showDailySteps) {
ActivityDetectionReceiver.registerFence(context)
} else {
ActivityDetectionReceiver.unregisterFence(context)
}
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(context)
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
requireFitnessPermission()
}
} else {
ActivityDetectionReceiver.unregisterFence(context)
}
statusCallback?.invoke()
}
private fun checkGoogleFitConnection() {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS
)) {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
binding.actionConnectToGoogleFit.isVisible = true
binding.actionDisconnectToGoogleFit.isVisible = false
binding.actionConnectToGoogleFit.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
binding.actionDisconnectToGoogleFit.setOnClickListener(null)
binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_not_connected)
} else {
binding.actionConnectToGoogleFit.isVisible = false
binding.actionDisconnectToGoogleFit.isVisible = true
binding.actionConnectToGoogleFit.setOnClickListener(null)
binding.actionDisconnectToGoogleFit.setOnClickListener {
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS
).build()).signOut().addOnCompleteListener {
show()
}
}
binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_connected)
}
}
private fun requireFitnessPermission() {
Dexter.withContext(context)
.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?) {
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()
}
} }

View File

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

View File

@ -10,7 +10,6 @@ object Actions {
const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT"
const val ACTION_GO_TO_NEXT_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_NEXT_EVENT" const val ACTION_GO_TO_NEXT_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_NEXT_EVENT"
const val ACTION_GO_TO_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT" const val ACTION_GO_TO_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT"
const val ACTION_REPORT_CRASH = "com.tommasoberlose.anotherwidget.action.REPORT_CRASH"
const val ACTION_CLEAR_NOTIFICATION = "com.tommasoberlose.anotherwidget.action.CLEAR_NOTIFICATION" const val ACTION_CLEAR_NOTIFICATION = "com.tommasoberlose.anotherwidget.action.CLEAR_NOTIFICATION"
const val ACTION_UPDATE_GREETINGS = "com.tommasoberlose.anotherwidget.action.UPDATE_GREETINGS" const val ACTION_UPDATE_GREETINGS = "com.tommasoberlose.anotherwidget.action.UPDATE_GREETINGS"

View File

@ -23,12 +23,6 @@ object Preferences : KotprefModel() {
var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "") var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "")
var nextEventId by longPref(key = "PREF_NEXT_EVENT_ID", default = -1) var nextEventId by longPref(key = "PREF_NEXT_EVENT_ID", default = -1)
var nextEventName by stringPref(key = "PREF_NEXT_EVENT_NAME")
var nextEventStartDate by longPref(key = "PREF_NEXT_EVENT_START_DATE")
var nextEventAllDay by booleanPref(key = "PREF_NEXT_EVENT_ALL_DAY")
var nextEventLocation by stringPref(key = "PREF_NEXT_EVENT_LOCATION")
var nextEventEndDate by longPref(key = "PREF_NEXT_EVENT_END_DATE")
var nextEventCalendarId by intPref(key = "PREF_NEXT_EVENT_CALENDAR_ID")
var customLocationLat by stringPref(key = "PREF_CUSTOM_LOCATION_LAT", default = "") var customLocationLat by stringPref(key = "PREF_CUSTOM_LOCATION_LAT", default = "")
var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "") var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "")
var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", default = "") var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", default = "")
@ -85,7 +79,7 @@ object Preferences : KotprefModel() {
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue) var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// UI // UI
var widgetMargin by floatPref(default = Constants.Dimension.SMALL.rawValue) var widgetMargin by floatPref(default = Constants.Dimension.NONE.rawValue)
var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue) var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue)
// Clock // Clock
@ -93,11 +87,11 @@ object Preferences : KotprefModel() {
var altTimezoneId by stringPref(default = "") var altTimezoneId by stringPref(default = "")
// Global // Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f) var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 24f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f) var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 16f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 26f) var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 72f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue) var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.rawValue) var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.SMALL.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false) var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "") var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "") var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")

View File

@ -25,6 +25,7 @@ object ActiveNotificationsHelper {
remove(Preferences::lastNotificationIcon) remove(Preferences::lastNotificationIcon)
} }
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
NotificationListener.clearTimeout(context)
} }
fun checkNotificationAccess(context: Context): Boolean { fun checkNotificationAccess(context: Context): Boolean {

View File

@ -5,10 +5,9 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -44,14 +43,14 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply { val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE action = Actions.ACTION_ALARM_UPDATE
} }
setExact( setExactIfCanSchedule(
AlarmManager.RTC, AlarmManager.RTC,
trigger, trigger,
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
context, context,
ALARM_UPDATE_ID, ALARM_UPDATE_ID,
intent, intent,
0 PendingIntent.FLAG_IMMUTABLE
) )
) )
} }
@ -62,7 +61,7 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply { val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE action = Actions.ACTION_ALARM_UPDATE
} }
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, 0)) cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, PendingIntent.FLAG_IMMUTABLE))
} }
} }

View File

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

View File

@ -4,10 +4,9 @@ import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.CalendarContract import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.services.UpdateCalendarService import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider import me.everything.providers.android.calendar.CalendarProvider
import java.util.* import java.util.*
@ -19,7 +18,7 @@ import kotlin.collections.ArrayList
object CalendarHelper { object CalendarHelper {
fun updateEventList(context: Context) { fun updateEventList(context: Context) {
UpdateCalendarService.enqueueWork(context) UpdateCalendarWorker.enqueue(context)
} }
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> { fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -50,11 +49,11 @@ object CalendarHelper {
} }
fun setEventUpdatesAndroidN(context: Context) { fun setEventUpdatesAndroidN(context: Context) {
EventListenerJob.schedule(context) UpdateCalendarWorker.enqueueTrigger(context)
} }
fun removeEventUpdatesAndroidN(context: Context) { fun removeEventUpdatesAndroidN(context: Context) {
EventListenerJob.remove(context) UpdateCalendarWorker.cancelTrigger(context)
} }
fun List<Event>.applyFilters() : List<Event> { fun List<Event>.applyFilters() : List<Event> {

View File

@ -37,7 +37,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS action = Actions.ACTION_UPDATE_GREETINGS
}, },
0) PendingIntent.FLAG_IMMUTABLE)
) )
setRepeating( setRepeating(
@ -51,7 +51,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS action = Actions.ACTION_UPDATE_GREETINGS
}, },
0) PendingIntent.FLAG_IMMUTABLE)
) )
setRepeating( setRepeating(
@ -65,7 +65,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS action = Actions.ACTION_UPDATE_GREETINGS
}, },
0) PendingIntent.FLAG_IMMUTABLE)
) )
setRepeating( setRepeating(
@ -79,14 +79,14 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS action = Actions.ACTION_UPDATE_GREETINGS
}, },
0) PendingIntent.FLAG_IMMUTABLE)
) )
} else { } else {
listOf(MORNING_TIME, MORNING_TIME_END, EVENING_TIME, NIGHT_TIME).forEach { listOf(MORNING_TIME, MORNING_TIME_END, EVENING_TIME, NIGHT_TIME).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context, cancel(PendingIntent.getBroadcast(context, it, Intent(context,
UpdatesReceiver::class.java).apply { UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS action = Actions.ACTION_UPDATE_GREETINGS
}, 0)) }, PendingIntent.FLAG_IMMUTABLE))
} }
} }
} }
@ -102,7 +102,7 @@ object GreetingsHelper {
val array = when { val array = when {
hour in 5..8 -> context.resources.getStringArray(R.array.morning_greetings) hour in 5..8 -> context.resources.getStringArray(R.array.morning_greetings)
hour in 19..21 -> context.resources.getStringArray(R.array.evening_greetings) hour in 19..21 -> context.resources.getStringArray(R.array.evening_greetings)
hour >= 22 && hour < 5 -> context.resources.getStringArray(R.array.night_greetings) hour >= 22 || hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
else -> emptyArray() else -> emptyArray()
} }
return if (array.isNotEmpty()) array[Random().nextInt(array.size)] else "" return if (array.isNotEmpty()) array[Random().nextInt(array.size)] else ""

View File

@ -17,7 +17,7 @@ object ImageHelper {
0 -> 0f * factor 0 -> 0f * factor
1 -> 8f * factor 1 -> 8f * factor
2 -> 16f * factor 2 -> 16f * factor
else -> 0f * factor else -> 8f * factor
}, resources.displayMetrics) }, resources.displayMetrics)
if (originalView.drawable != null && originalView.drawable.intrinsicWidth > 0 && originalView.drawable.intrinsicHeight > 0) { if (originalView.drawable != null && originalView.drawable.intrinsicWidth > 0 && originalView.drawable.intrinsicHeight > 0) {
@ -58,7 +58,7 @@ object ImageHelper {
0 -> 0f * factor 0 -> 0f * factor
1 -> 0.8f * factor 1 -> 0.8f * factor
2 -> 1f * factor 2 -> 1f * factor
else -> 0f else -> 0.8f * factor
})) }))
colorMatrixScript.setColorMatrix(matrix) colorMatrixScript.setColorMatrix(matrix)

View File

@ -32,7 +32,7 @@ object IntentHelper {
return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK) return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
PendingIntent.getActivity(context, requestCode, intent, flags) PendingIntent.getActivity(context, requestCode, intent, flags)
else else
PendingIntent.getBroadcast(context, requestCode, intent, 0) PendingIntent.getBroadcast(context, requestCode, intent, flags)
} }
fun getWidgetUpdateIntent(context: Context): Intent { fun getWidgetUpdateIntent(context: Context): Intent {

View File

@ -7,7 +7,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.services.LocationService import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDarkTheme import com.tommasoberlose.anotherwidget.utils.isDarkTheme
@ -19,18 +19,10 @@ import com.tommasoberlose.anotherwidget.utils.isDarkTheme
object WeatherHelper { object WeatherHelper {
suspend fun updateWeather(context: Context) { fun updateWeather(context: Context, force: Boolean = false) {
Kotpref.init(context) if (Preferences.showWeather || force)
if (Preferences.customLocationAdd != "") { WeatherWorker.enqueue(context, replace = force)
WeatherNetworkApi(context).updateWeather() else {
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
context.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
LocationService.requestNewLocation(context)
} else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
removeWeather(context) removeWeather(context)
org.greenrobot.eventbus.EventBus.getDefault().post( org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent() com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
@ -339,92 +331,94 @@ object WeatherHelper {
} }
} }
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when { fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (
iconString.contains("skc") -> "01" iconString.substringBefore('?').substringAfterLast('/').substringBefore(',')
iconString.contains("few") -> "02" ) {
iconString.contains("sct") -> "03" "skc" -> "01"
iconString.contains("bkn") -> "04" "few" -> "02"
iconString.contains("ovc") -> "04" "sct" -> "02"
iconString.contains("wind_skc") -> "01" "bkn" -> "03"
iconString.contains("wind_few") -> "02" "ovc" -> "04"
iconString.contains("wind_sct") -> "03" "wind_skc" -> "01"
iconString.contains("wind_bkn") -> "04" "wind_few" -> "02"
iconString.contains("wind_ovc") -> "04" "wind_sct" -> "02"
iconString.contains("snow") -> "13" "wind_bkn" -> "03"
iconString.contains("rain_snow") -> "81" "wind_ovc" -> "04"
iconString.contains("rain_sleet") -> "81" "snow" -> "13"
iconString.contains("snow_sleet") -> "81" "rain_snow" -> "81"
iconString.contains("fzra") -> "81" "rain_sleet" -> "81"
iconString.contains("rain_fzra") -> "81" "snow_sleet" -> "81"
iconString.contains("snow_fzra") -> "81" "fzra" -> "81"
iconString.contains("sleet") -> "81" "rain_fzra" -> "81"
iconString.contains("rain") -> "10" "snow_fzra" -> "81"
iconString.contains("rain_showers") -> "10" "sleet" -> "81"
iconString.contains("rain_showers_hi") -> "10" "rain" -> "10"
iconString.contains("tsra") -> "82" "rain_showers" -> "10"
iconString.contains("tsra_sct") -> "82" "rain_showers_hi" -> "10"
iconString.contains("tsra_hi") -> "82" "tsra" -> "09"
iconString.contains("tornado") -> "80" "tsra_sct" -> "11"
iconString.contains("hurricane") -> "80" "tsra_hi" -> "11"
iconString.contains("tropical_storm") -> "09" "tornado" -> "80"
iconString.contains("dust") -> "Dust" "hurricane" -> "80"
iconString.contains("smoke") -> "Smoke" "tropical_storm" -> "09"
iconString.contains("haze") -> "50" "dust" -> "50"
iconString.contains("hot") -> "01" "smoke" -> "50"
iconString.contains("cold") -> "13" "haze" -> "50"
iconString.contains("blizzard") -> "80" "hot" -> "01"
iconString.contains("fog") -> "82" "cold" -> "13"
"blizzard" -> "13"
"fog" -> "82"
else -> "" else -> ""
} + if (isDaytime) "d" else "n" } + if (isDaytime) "d" else "n"
fun getWeatherBitIcon(iconString: String): String = when { fun getWeatherBitIcon(iconString: String): String = when (iconString.substring(0, 3)) {
iconString.contains("t01") -> "11" "t01" -> "11"
iconString.contains("t02") -> "09" "t02" -> "11"
iconString.contains("t03") -> "09" "t03" -> "09"
iconString.contains("t04") -> "09" "t04" -> "11"
iconString.contains("t05") -> "09" "t05" -> "11"
iconString.contains("d01") -> "10" "d01" -> "10"
iconString.contains("d02") -> "10" "d02" -> "10"
iconString.contains("d03") -> "10" "d03" -> "10"
iconString.contains("r01") -> "10" "r01" -> "10"
iconString.contains("r02") -> "10" "r02" -> "10"
iconString.contains("r03") -> "10" "r03" -> "10"
iconString.contains("f01") -> "10" "f01" -> "10"
iconString.contains("r04") -> "10" "r04" -> "10"
iconString.contains("r05") -> "10" "r05" -> "10"
iconString.contains("r06") -> "10" "r06" -> "10"
iconString.contains("s01") -> "13" "s01" -> "13"
iconString.contains("s02") -> "13" "s02" -> "13"
iconString.contains("s03") -> "13" "s03" -> "13"
iconString.contains("s04") -> "81" "s04" -> "81"
iconString.contains("s05") -> "90" "s05" -> "81"
iconString.contains("s06") -> "13" "s06" -> "13"
iconString.contains("a01") -> "82" "a01" -> "50"
iconString.contains("a02") -> "82" "a02" -> "50"
iconString.contains("a03") -> "82" "a03" -> "50"
iconString.contains("a04") -> "82" "a04" -> "50"
iconString.contains("a05") -> "82" "a05" -> "82"
iconString.contains("a06") -> "82" "a06" -> "82"
iconString.contains("c01") -> "01" "c01" -> "01"
iconString.contains("c02") -> "02" "c02" -> "02"
iconString.contains("c03") -> "04" "c03" -> "03"
iconString.contains("c04") -> "04" "c04" -> "04"
else -> "" else -> ""
} + if (iconString.contains("d")) "d" else "n" } + iconString.substring(3)
fun getWeatherApiIcon(icon: Int, isDaytime: Boolean): String = when(icon) { fun getWeatherApiIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1000 -> "01" 1000 -> "01"
1003 -> "02" 1003 -> "02"
1006 -> "03" 1006 -> "03"
1009 -> "04" 1009 -> "04"
1030 -> "82" 1030 -> "50"
1063 -> "10" 1063 -> "10"
1066 -> "10" 1066 -> "13"
1069 -> "10" 1069 -> "81"
1072 -> "81" 1072 -> "81"
1087 -> "11" 1087 -> "11"
1114 -> "13" 1114 -> "13"
1117 -> "09" 1117 -> "13"
1135 -> "82" 1135 -> "82"
1147 -> "82" 1147 -> "82"
1150 -> "10" 1150 -> "10"
@ -439,8 +433,8 @@ object WeatherHelper {
1195 -> "10" 1195 -> "10"
1198 -> "81" 1198 -> "81"
1201 -> "81" 1201 -> "81"
1204 -> "13" 1204 -> "81"
1207 -> "13" 1207 -> "81"
1210 -> "13" 1210 -> "13"
1213 -> "13" 1213 -> "13"
1216 -> "13" 1216 -> "13"
@ -451,62 +445,62 @@ object WeatherHelper {
1240 -> "10" 1240 -> "10"
1243 -> "10" 1243 -> "10"
1246 -> "10" 1246 -> "10"
1249 -> "13" 1249 -> "81"
1252 -> "13" 1252 -> "81"
1255 -> "13" 1255 -> "13"
1258 -> "13" 1258 -> "13"
1261 -> "13" 1261 -> "13"
1264 -> "13" 1264 -> "13"
1273 -> "09" 1273 -> "11"
1276 -> "09" 1276 -> "09"
1279 -> "13" 1279 -> "13"
1282 -> "13" 1282 -> "13"
else -> "" else -> ""
} + if (isDaytime) "d" else "n" } + if (isDaytime) "d" else "n"
fun getYRIcon(iconCode: String, isDaytime: Boolean): String = when { fun getYRIcon(iconCode: String): String = when (iconCode.substringBefore('_')) {
iconCode.contains("clearsky") -> "01" "clearsky" -> "01"
iconCode.contains("cloudy") -> "04" "cloudy" -> "04"
iconCode.contains("fair") -> "02" "fair" -> "02"
iconCode.contains("fog") -> "82" "fog" -> "82"
iconCode.contains("heavyrain") -> "10" "heavyrain" -> "10"
iconCode.contains("heavyrainandthunder") -> "11" "heavyrainandthunder" -> "09"
iconCode.contains("heavyrainshowers") -> "10" "heavyrainshowers" -> "10"
iconCode.contains("heavyrainshowersandthunder") -> "11" "heavyrainshowersandthunder" -> "09"
iconCode.contains("heavysleet") -> "10" "heavysleet" -> "81"
iconCode.contains("heavysleetandthunder") -> "11" "heavysleetandthunder" -> "81"
iconCode.contains("heavysleetshowers") -> "10" "heavysleetshowers" -> "81"
iconCode.contains("heavysleetshowersandthunder") -> "11" "heavysleetshowersandthunder" -> "81"
iconCode.contains("heavysnow") -> "13" "heavysnow" -> "13"
iconCode.contains("heavysnowandthunder") -> "13" "heavysnowandthunder" -> "13"
iconCode.contains("heavysnowshowers") -> "13" "heavysnowshowers" -> "13"
iconCode.contains("heavysnowshowersandthunder") -> "13" "heavysnowshowersandthunder" -> "13"
iconCode.contains("lightrain") -> "10" "lightrain" -> "10"
iconCode.contains("lightrainandthunder") -> "11" "lightrainandthunder" -> "11"
iconCode.contains("lightrainshowers") -> "10" "lightrainshowers" -> "10"
iconCode.contains("lightrainshowersandthunder") -> "11" "lightrainshowersandthunder" -> "11"
iconCode.contains("lightsleet") -> "10" "lightsleet" -> "81"
iconCode.contains("lightsleetandthunder") -> "11" "lightsleetandthunder" -> "81"
iconCode.contains("lightsleetshowers") -> "10" "lightsleetshowers" -> "81"
iconCode.contains("lightsnow") -> "13" "lightsnow" -> "13"
iconCode.contains("lightsnowandthunder") -> "13" "lightsnowandthunder" -> "13"
iconCode.contains("lightsnowshowers") -> "13" "lightsnowshowers" -> "13"
iconCode.contains("lightssleetshowersandthunder") -> "81" "lightssleetshowersandthunder" -> "81"
iconCode.contains("lightssnowshowersandthunder") -> "81" "lightssnowshowersandthunder" -> "81"
iconCode.contains("partlycloudy") -> "03" "partlycloudy" -> "03"
iconCode.contains("rain") -> "10" "rain" -> "10"
iconCode.contains("rainandthunder") -> "11" "rainandthunder" -> "11"
iconCode.contains("rainshowers") -> "10" "rainshowers" -> "10"
iconCode.contains("rainshowersandthunder") -> "11" "rainshowersandthunder" -> "11"
iconCode.contains("sleet") -> "10" "sleet" -> "81"
iconCode.contains("sleetandthunder") -> "11" "sleetandthunder" -> "81"
iconCode.contains("sleetshowers") -> "10" "sleetshowers" -> "81"
iconCode.contains("sleetshowersandthunder") -> "11" "sleetshowersandthunder" -> "81"
iconCode.contains("snow") -> "13" "snow" -> "13"
iconCode.contains("snowandthunder") -> "13" "snowandthunder" -> "13"
iconCode.contains("snowshowers") -> "13" "snowshowers" -> "13"
iconCode.contains("snowshowersandthunder") -> "13" "snowshowersandthunder" -> "13"
else -> "" else -> ""
} + if (isDaytime) "d" else "n" } + if (iconCode.substringAfter('_', "day") == "day") "d" else "n"
} }

View File

@ -6,17 +6,10 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import androidx.core.provider.FontRequest import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlin.math.min
object WidgetHelper { object WidgetHelper {
class WidgetSizeProvider( class WidgetSizeProvider(
@ -25,12 +18,17 @@ object WidgetHelper {
) { ) {
fun getWidgetsSize(widgetId: Int): Pair<Int, Int> { fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
val width = getWidgetWidth(widgetId) val portrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT
val height = getWidgetHeight(widgetId) val width = getWidgetSizeInDp(
widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
)
val height = getWidgetSizeInDp(
widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
)
val widthInPx = context.dip(width) val widthInPx = context.dip(width)
val heightInPx = context.dip(height) val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
FirebaseCrashlytics.getInstance().setCustomKey("heightInPx", heightInPx)
return widthInPx to heightInPx return widthInPx to heightInPx
} }

View File

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

View File

@ -26,9 +26,7 @@ class TimeZonesApi(val context: Context) {
when (val response = repository.getTimeZone(lat, long)) { when (val response = repository.getTimeZone(lat, long)) {
is NetworkResponse.Success -> { is NetworkResponse.Success -> {
try { try {
Log.d("ciao", response.body.toString())
id = response.body["timezoneId"] as String id = response.body["timezoneId"] as String
} catch(ex: Exception) { } catch(ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
} }

View File

@ -17,13 +17,11 @@ import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.repository.* import com.tommasoberlose.anotherwidget.network.repository.*
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import java.lang.Exception import java.lang.Exception
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import org.greenrobot.eventbus.EventBus
class WeatherNetworkApi(val context: Context) { class WeatherNetworkApi(val context: Context) {
suspend fun updateWeather() { suspend fun updateWeather() {
@ -31,7 +29,7 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = "-" Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
if (Preferences.showWeather && Preferences.customLocationLat != "" && Preferences.customLocationLon != "") { if (Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) { when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> useOpenWeatherMap(context) Constants.WeatherProvider.OPEN_WEATHER -> useOpenWeatherMap(context)
Constants.WeatherProvider.WEATHER_GOV -> useWeatherGov(context) Constants.WeatherProvider.WEATHER_GOV -> useWeatherGov(context)
@ -42,46 +40,67 @@ class WeatherNetworkApi(val context: Context) {
Constants.WeatherProvider.YR -> useYrProvider(context) Constants.WeatherProvider.YR -> useYrProvider(context)
} }
} else { } else {
if (!Preferences.showWeather)
Preferences.weatherProviderError = context.getString(R.string.show_weather_not_visible)
else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location) Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
}
WeatherHelper.removeWeather( WeatherHelper.removeWeather(
context context
) )
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent()) EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
} }
private fun useOpenWeatherMap(context: Context) { private suspend fun useOpenWeatherMap(context: Context) {
if (Preferences.weatherProviderApiOpen != "") { if (Preferences.weatherProviderApiOpen != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen) val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen)
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC) helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC)
when (val response = suspendCancellableCoroutine<Any?> { continuation ->
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object : helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback { CurrentWeatherCallback {
override fun onSuccess(currentWeather: CurrentWeather?) { override fun onSuccess(currentWeather: CurrentWeather?) {
currentWeather?.let { continuation.resume(currentWeather)
Preferences.weatherTemp = currentWeather.main.temp.toFloat()
Preferences.weatherIcon = currentWeather.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
override fun onFailure(throwable: Throwable?) { override fun onFailure(throwable: Throwable?) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) continuation.resume(throwable)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
}) })
}) {
is CurrentWeather -> {
Preferences.weatherTemp = response.main.temp.toFloat()
Preferences.weatherIcon = response.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
is Throwable -> {
if (response.javaClass == Throwable::class.java) {
// server error, see [OpenWeatherMapHelper.handleCurrentWeatherResponse]
if (response.message?.startsWith("UnAuthorized") == true) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
WeatherHelper.removeWeather(
context
)
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else { } else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
@ -121,8 +140,17 @@ class WeatherNetworkApi(val context: Context) {
val props = val props =
weatherResponse.body["properties"] as LinkedTreeMap<*, *> weatherResponse.body["properties"] as LinkedTreeMap<*, *>
val periods = props["periods"] as List<*> val periods = props["periods"] as List<*>
val now = periods[0] as LinkedTreeMap<*, *> @android.annotation.SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
"yyyy-MM-dd'T'HH:mm:ssXXX"
else
"yyyy-MM-dd'T'HH:mm:ssZ"
)
for (period in periods) {
val now = period as LinkedTreeMap<*, *>
val endTime = format.parse(now["endTime"] as String)!!
if (endTime.time > System.currentTimeMillis()) {
val temp = now["temperature"] as Double val temp = now["temperature"] as Double
val fullIcon = now["icon"] as String val fullIcon = now["icon"] as String
val isDaytime = now["isDaytime"] as Boolean val isDaytime = now["isDaytime"] as Boolean
@ -130,16 +158,16 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherTemp = temp.toFloat() Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherGovIcon(fullIcon, isDaytime) Preferences.weatherIcon = WeatherHelper.getWeatherGovIcon(fullIcon, isDaytime)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
break
MainWidget.updateWidget(context) }
}
} catch (ex: Exception) { } catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
} }
else -> { else -> {
@ -155,14 +183,16 @@ class WeatherNetworkApi(val context: Context) {
} }
} }
is NetworkResponse.ServerError -> { is NetworkResponse.ServerError -> {
if (pointsResponse.body?.containsKey("status") == true && (pointsResponse.body?.get("status") as Double).toInt() == 404) { when (pointsResponse.code) {
404 -> {
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_wrong_location) Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_wrong_location)
} else { }
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
} }
}
WeatherHelper.removeWeather( WeatherHelper.removeWeather(
context context
) )
@ -183,7 +213,18 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) { when (val response = repository.getWeather()) {
is NetworkResponse.Success -> { is NetworkResponse.Success -> {
try { try {
Log.d("ciao - here", response.body.toString()) val observations = response.body["observations"] as LinkedTreeMap<*, *>
val location = (observations["location"] as List<*>).first() as LinkedTreeMap<*, *>
val observation = (location["observation"] as List<*>).first() as LinkedTreeMap<*, *>
val iconName = observation["iconName"] as String
val daylight = observation["daylight"] as String
val temperature = observation["temperature"] as String
Preferences.weatherTemp = temperature.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(iconName, daylight != "N")
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
} catch(ex: Exception) { } catch(ex: Exception) {
@ -194,8 +235,16 @@ class WeatherNetworkApi(val context: Context) {
} }
} }
is NetworkResponse.ServerError -> { is NetworkResponse.ServerError -> {
when (response.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather( WeatherHelper.removeWeather(
context context
) )
@ -225,10 +274,10 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) { when (val response = repository.getWeather()) {
is NetworkResponse.Success -> { is NetworkResponse.Success -> {
try { try {
val data = response.body["data"] as List<LinkedTreeMap<String, Any>>? val data = response.body["data"] as List<*>?
data?.first()?.let { data?.first()?.let { it as LinkedTreeMap<*, *>
val temp = it["temp"] as Double val temp = it["temp"] as Double
val weatherInfo = it["weather"] as LinkedTreeMap<String, Any> val weatherInfo = it["weather"] as LinkedTreeMap<*, *>
val iconCode = weatherInfo["icon"] as String val iconCode = weatherInfo["icon"] as String
Preferences.weatherTemp = temp.toFloat() Preferences.weatherTemp = temp.toFloat()
@ -238,8 +287,6 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
} catch(ex: Exception) { } catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
@ -288,12 +335,12 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) { when (val response = repository.getWeather()) {
is NetworkResponse.Success -> { is NetworkResponse.Success -> {
try { try {
val current = response.body["current"] as LinkedTreeMap<String, Any>? val current = response.body["current"] as LinkedTreeMap<*, *>?
current?.let { current?.let {
val tempC = current["temp_c"] as Double val tempC = current["temp_c"] as Double
val tempF = current["temp_f"] as Double val tempF = current["temp_f"] as Double
val isDay = current["is_day"] as Double val isDay = current["is_day"] as Double
val condition = current["condition"] as LinkedTreeMap<String, Any> val condition = current["condition"] as LinkedTreeMap<*, *>
val iconCode = condition["code"] as Double val iconCode = condition["code"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat() Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
@ -303,8 +350,6 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} }
} catch(ex: Exception) { } catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
@ -353,28 +398,74 @@ class WeatherNetworkApi(val context: Context) {
private suspend fun useAccuweatherProvider(context: Context) { private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") { if (Preferences.weatherProviderApiAccuweather != "") {
// val repository = AccuweatherRepository() val repository = AccuweatherRepository()
// when (val response = repository.getWeather()) { when (val locationResponse = repository.getLocation()) {
// is NetworkResponse.Success -> { is NetworkResponse.Success -> {
// try { try {
// Log.d("ciao", response.body.toString()) val key = locationResponse.body["Key"] as String
// } catch(ex: Exception) {
//
// Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
// Preferences.weatherProviderLocationError = ""
// }
// }
// is NetworkResponse.ServerError -> {
// WeatherHelper.removeWeather(
// context
// )
// }
// Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
// Preferences.weatherProviderLocationError = ""
// EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
// }
when (val weatherResponse = repository.getWeather(key)) {
is NetworkResponse.Success -> {
try {
weatherResponse.body.first().let {
val temp = it["Temperature"] as LinkedTreeMap<*, *>
val tempC = (temp["Metric"] as LinkedTreeMap<*, *>)["Value"] as Double
val tempF = (temp["Imperial"] as LinkedTreeMap<*, *>)["Value"] as Double
val isDay = it["IsDayTime"] as Boolean
val icon = it["WeatherIcon"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(icon.toInt(), isDay)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (locationResponse.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
503 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_expired_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else { } else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
@ -393,14 +484,9 @@ class WeatherNetworkApi(val context: Context) {
is NetworkResponse.Success -> { is NetworkResponse.Success -> {
try { try {
val pp = response.body["properties"] as LinkedTreeMap<*, *> val pp = response.body["properties"] as LinkedTreeMap<*, *>
val data = pp["timeseries"] as List<LinkedTreeMap<String, Any>>? val data = pp["timeseries"] as List<*>?
data?.let { data?.first()?.let { it as LinkedTreeMap<*, *>
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") val dd = it["data"] as LinkedTreeMap<*, *>
for (item in data) {
val time = Calendar.getInstance().apply { time = format.parse(item["time"] as String)!! }
val now = Calendar.getInstance()
if (time.timeInMillis >= now.timeInMillis) {
val dd = item["data"] as LinkedTreeMap<*, *>
val instant = dd["instant"] as LinkedTreeMap<*, *> val instant = dd["instant"] as LinkedTreeMap<*, *>
val next = dd["next_1_hours"] as LinkedTreeMap<*, *> val next = dd["next_1_hours"] as LinkedTreeMap<*, *>
@ -411,20 +497,14 @@ class WeatherNetworkApi(val context: Context) {
val iconCode = summary["symbol_code"] as String val iconCode = summary["symbol_code"] as String
Preferences.weatherTemp = temp.toFloat() Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getYRIcon(iconCode, now.get(Calendar.HOUR_OF_DAY) >= 22 || now.get(Calendar.HOUR_OF_DAY) <= 8) Preferences.weatherIcon = WeatherHelper.getYRIcon(iconCode)
Preferences.weatherTempUnit = "C" Preferences.weatherTempUnit = "C"
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
break
} }
}
}
} catch(ex: Exception) { } catch(ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic) Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)

View File

@ -13,7 +13,7 @@ object ApiServices {
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>> ): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)") @Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("gridpoints/{gridId}/{gridX},{gridY}/forecast") @GET("gridpoints/{gridId}/{gridX},{gridY}/forecast/hourly")
suspend fun getWeather( suspend fun getWeather(
@Path("gridId") gridId: String, @Path("gridId") gridId: String,
@Path("gridX") gridX: Int, @Path("gridX") gridX: Int,
@ -54,13 +54,17 @@ object ApiServices {
} }
interface AccuweatherService { interface AccuweatherService {
@GET("") @GET("locations/v1/cities/geoposition/search")
suspend fun getWeather( suspend fun getLocation(
@Path("gridId") gridId: String, @Query("apikey") apikey: String,
@Path("gridX") gridX: Int, @Query("q") location: String
@Path("gridY") gridY: Int,
@Query("units") unit: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>> ): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@GET("currentconditions/v1/{locationKey}")
suspend fun getWeather(
@Path("locationKey") locationKey: String,
@Query("apikey") apikey: String
): NetworkResponse<List<HashMap<String, Any>>, HashMap<String, Any>>
} }
interface YrService { interface YrService {

View File

@ -1,6 +1,7 @@
package com.tommasoberlose.anotherwidget.network.repository package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@ -9,10 +10,11 @@ class AccuweatherRepository {
/* ACCUWEATHER */ /* ACCUWEATHER */
private val apiServiceAccu: ApiServices.AccuweatherService = getRetrofit().create(ApiServices.AccuweatherService::class.java) private val apiServiceAccu: ApiServices.AccuweatherService = getRetrofit().create(ApiServices.AccuweatherService::class.java)
suspend fun getWeather(): Nothing = TODO() suspend fun getLocation() = apiServiceAccu.getLocation(Preferences.weatherProviderApiAccuweather, "${Preferences.customLocationLat},${Preferences.customLocationLon}")
suspend fun getWeather(locationKey: String) = apiServiceAccu.getWeather(locationKey, Preferences.weatherProviderApiAccuweather)
companion object { companion object {
private const val BASE_URL_ACCU = "" private const val BASE_URL_ACCU = "https://dataservice.accuweather.com/"
private fun getRetrofit(): Retrofit { private fun getRetrofit(): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
@ -22,4 +24,20 @@ class AccuweatherRepository {
.build() .build()
} }
} }
fun getWeatherIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1, 2, 30, 33, 34 -> "01"
3, 4, 35, 36 -> "02"
5, 37 -> "50"
6, 38 -> "03"
7, 8 -> "04"
11 -> "82"
12, 13, 14, 18, 39, 40 -> "10"
15 -> "09"
16, 17, 41, 42 -> "11"
32 -> "80"
19, 20, 21, 22, 23, 24, 31, 43, 44 -> "13"
25, 26, 29 -> "81"
else -> ""
} + if (isDaytime) "d" else "n"
} }

View File

@ -23,4 +23,157 @@ class HereRepository {
.build() .build()
} }
} }
fun getWeatherIcon(iconName: String, isDaytime: Boolean): String = when(iconName.substringAfter("night_")) {
"sunny" -> "01"
"clear" -> "01"
"mostly_sunny" -> "01"
"mostly_clear" -> "01"
"passing_clounds" -> "02"
"more_sun_than_clouds" -> "02"
"scattered_clouds" -> "02"
"partly_cloudy" -> "02"
"a_mixture_of_sun_and_clouds" -> "03"
"increasing_cloudiness" -> "03"
"breaks_of_sun_late" -> "03"
"afternoon_clouds" -> "03"
"morning_clouds" -> "03"
"partly_sunny" -> "03"
"high_level_clouds" -> "03"
"decreasing_cloudiness" -> "03"
"clearing_skies" -> "01"
"high_clouds" -> "03"
"rain_early" -> "10"
"heavy_rain_early" -> "10"
"strong_thunderstorms" -> "09"
"severe_thunderstorms" -> "09"
"thundershowers" -> "11"
"thunderstorms" -> "11"
"tstorms_early" -> "11"
"isolated_tstorms_late" -> "11"
"scattered_tstorms_late" -> "11"
"tstorms_late" -> "11"
"tstorms" -> "11"
"ice_fog" -> "82"
"more_clouds_than_sun" -> "03"
"broken_clouds" -> "03"
"scattered_showers" -> "10"
"a_few_showers" -> "10"
"light_showers" -> "10"
"passing_showers" -> "10"
"rain_showers" -> "10"
"showers" -> "10"
"widely_scattered_tstorms" -> "11"
"isolated_tstorms" -> "11"
"a_few_tstorms" -> "11"
"scattered_tstorms" -> "11"
"hazy_sunshine" -> "50"
"haze" -> "50"
"smoke" -> "50"
"low_level_haze" -> "50"
"early_fog_followed_by_sunny_skies" -> "50"
"early_fog" -> "82"
"light_fog" -> "82"
"fog" -> "82"
"dense_fog" -> "82"
//"night_haze"
//"night_smoke"
//"night_low_level_haze"
//"night_widely_scattered_tstorms"
//"night_isolated_tstorms"
//"night_a_few_tstorms"
//"night_scattered_tstorms"
//"night_tstorms"
//"night_clear"
"mostly_cloudy" -> "03"
"cloudy" -> "04"
"overcast" -> "04"
"low_clouds" -> "03"
"hail" -> "10"
"sleet" -> "81"
"light_mixture_of_precip" -> "81"
"icy_mix" -> "81"
"mixture_of_precip" -> "81"
"heavy_mixture_of_precip" -> "81"
"snow_changing_to_rain" -> "81"
"snow_changing_to_an_icy_mix" -> "81"
"an_icy_mix_changing_to_snow" -> "81"
"an_icy_mix_changing_to_rain" -> "81"
"rain_changing_to_snow" -> "81"
"rain_changing_to_an_icy_mix" -> "81"
"light_icy_mix_early" -> "81"
"icy_mix_early" -> "81"
"light_icy_mix_late" -> "81"
"icy_mix_late" -> "81"
"snow_rain_mix" -> "81"
"scattered_flurries" -> "13"
"snow_flurries" -> "13"
"light_snow_showers" -> "13"
"snow_showers" -> "13"
"light_snow" -> "13"
"flurries_early" -> "13"
"snow_showers_early" -> "13"
"light_snow_early" -> "13"
"flurries_late" -> "13"
"snow_showers_late" -> "13"
"light_snow_late" -> "13"
//"night_decreasing_cloudiness"
//"night_clearing_skies"
//"night_high_level_clouds"
//"night_high_clouds"
//"night_scattered_showers"
//"night_a_few_showers"
//"night_light_showers"
//"night_passing_showers"
//"night_rain_showers"
//"night_sprinkles"
//"night_showers"
//"night_mostly_clear"
//"night_passing_clouds"
//"night_scattered_clouds"
//"night_partly_cloudy"
//"increasing_cloudiness"
//"night_afternoon_clouds"
//"night_morning_clouds"
//"night_broken_clouds"
//"night_mostly_cloudy"
"light_freezing_rain" -> "81"
"freezing_rain" -> "81"
"heavy_rain" -> "10"
"lots_of_rain" -> "10"
"tons_of_rain" -> "10"
//"heavy_rain_early" -> "10"
"heavy_rain_late" -> "10"
"flash_floods" -> "10"
"flood" -> "10"
"drizzle" -> "10"
"sprinkles" -> "10"
"light_rain" -> "10"
"sprinkles_early" -> "10"
"light_rain_early" -> "10"
"sprinkles_late" -> "10"
"light_rain_late" -> "10"
"rain" -> "10"
"numerous_showers" -> "10"
"showery" -> "10"
"showers_early" -> "10"
//"rain_early" -> "10"
"showers_late" -> "10"
"rain_late" -> "10"
"snow" -> "13"
"moderate_snow" -> "13"
"snow_early" -> "13"
"snow_late" -> "13"
"heavy_snow" -> "13"
"heavy_snow_early" -> "13"
"heavy_snow_late" -> "13"
"tornado" -> "80"
"tropical_storm" -> "09"
"hurricane" -> "80"
"sandstorm" -> "50"
"duststorm" -> "50"
"snowstorm" -> "13"
"blizzard" -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
} }

View File

@ -1,181 +0,0 @@
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.chibatching.kotpref.blockingBulk
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.blockingBulk {
remove(Preferences::googleFitSteps)
}
}
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)
.aggregate(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))
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + 5 * 60 * 1000,
PendingIntent.getBroadcast(
context,
5,
Intent(context, ActivityDetectionReceiver::class.java),
0
)
)
}
}
}
}

View File

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

View File

@ -16,6 +16,7 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.lang.Exception import java.lang.Exception
import java.util.* import java.util.*
@ -23,19 +24,26 @@ import java.util.*
class NotificationListener : NotificationListenerService() { class NotificationListener : NotificationListenerService() {
override fun onListenerConnected() { override fun onListenerConnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this) MediaPlayerHelper.updatePlayingMediaInfo(this)
MainWidget.updateWidget(this) ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerConnected() super.onListenerConnected()
} }
override fun onListenerDisconnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) { override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.notification?.extras?.let { bundle -> sbn?.notification?.extras?.let { bundle ->
bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let { bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this) MediaPlayerHelper.updatePlayingMediaInfo(this)
} ?: run { } ?: run {
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0 val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0 val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) { if (Preferences.showNotifications && bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
Preferences.lastNotificationId = sbn.id Preferences.lastNotificationId = sbn.id
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: "" Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
try { try {
@ -59,15 +67,13 @@ class NotificationListener : NotificationListenerService() {
} }
override fun onNotificationRemoved(sbn: StatusBarNotification?) { override fun onNotificationRemoved(sbn: StatusBarNotification?) {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this) MediaPlayerHelper.updatePlayingMediaInfo(this)
sbn?.let { sbn?.let {
if (sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) { if (Preferences.showNotifications && sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
ActiveNotificationsHelper.clearLastNotification(this) ActiveNotificationsHelper.clearLastNotification(this)
} }
} }
MainWidget.updateWidget(this)
super.onNotificationRemoved(sbn) super.onNotificationRemoved(sbn)
} }
@ -76,10 +82,9 @@ class NotificationListener : NotificationListenerService() {
val intent = Intent(context, UpdatesReceiver::class.java).apply { val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION action = Actions.ACTION_CLEAR_NOTIFICATION
} }
cancel(PendingIntent.getBroadcast(context, 28943, intent, 0))
val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter) val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter)
if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) { if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) {
setExact( setExactIfCanSchedule(
AlarmManager.RTC, AlarmManager.RTC,
Calendar.getInstance().timeInMillis + when (timeoutPref) { Calendar.getInstance().timeInMillis + when (timeoutPref) {
Constants.GlanceNotificationTimer.HALF_MINUTE -> 30 * 1000 Constants.GlanceNotificationTimer.HALF_MINUTE -> 30 * 1000
@ -87,16 +92,27 @@ class NotificationListener : NotificationListenerService() {
Constants.GlanceNotificationTimer.FIVE_MINUTES -> 5 * 60 * 1000 Constants.GlanceNotificationTimer.FIVE_MINUTES -> 5 * 60 * 1000
Constants.GlanceNotificationTimer.TEN_MINUTES -> 10 * 60 * 1000 Constants.GlanceNotificationTimer.TEN_MINUTES -> 10 * 60 * 1000
Constants.GlanceNotificationTimer.FIFTEEN_MINUTES -> 15 * 60 * 1000 Constants.GlanceNotificationTimer.FIFTEEN_MINUTES -> 15 * 60 * 1000
else -> 0 else -> 60 * 1000
}, },
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
context, context,
5, 5,
intent, intent,
0 PendingIntent.FLAG_IMMUTABLE
) )
) )
} }
} }
} }
companion object {
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 5, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
}
} }

View File

@ -12,11 +12,9 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.* import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.models.Event import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.Dispatchers import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.joda.time.Period
import java.util.* import java.util.*
import org.joda.time.Period
class UpdatesReceiver : BroadcastReceiver() { class UpdatesReceiver : BroadcastReceiver() {
@ -27,18 +25,18 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED, Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIME_CHANGED, Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED, Intent.ACTION_LOCALE_CHANGED -> {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
ActiveNotificationsHelper.clearLastNotification(context)
GreetingsHelper.toggleGreetings(context)
}
Intent.ACTION_DATE_CHANGED, Intent.ACTION_DATE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> { Actions.ACTION_CALENDAR_UPDATE -> {
ActiveNotificationsHelper.clearLastNotification(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
CalendarHelper.updateEventList(context) CalendarHelper.updateEventList(context)
} }
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS,
Actions.ACTION_TIME_UPDATE -> { Actions.ACTION_TIME_UPDATE -> {
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) { if (intent.hasExtra(EVENT_ID)) {
@ -46,20 +44,24 @@ class UpdatesReceiver : BroadcastReceiver() {
} }
} }
Actions.ACTION_CLEAR_NOTIFICATION -> { "com.sec.android.widgetapp.APPWIDGET_RESIZE",
ActiveNotificationsHelper.clearLastNotification(context) AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context) MainWidget.updateWidget(context)
} }
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
}
Actions.ACTION_REFRESH -> { Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
CalendarHelper.updateEventList(context) CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context) MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context) WeatherHelper.updateWeather(context)
} }
} }
} }
}
companion object { companion object {
const val EVENT_ID = "EVENT_ID" const val EVENT_ID = "EVENT_ID"
@ -71,7 +73,7 @@ class UpdatesReceiver : BroadcastReceiver() {
if (eventId == null) { if (eventId == null) {
// schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works) // schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact( setExactIfCanSchedule(
AlarmManager.RTC, AlarmManager.RTC,
Calendar.getInstance().apply { Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
@ -86,7 +88,7 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE action = Actions.ACTION_CALENDAR_UPDATE
}, },
0 PendingIntent.FLAG_IMMUTABLE
) )
) )
} }
@ -95,7 +97,7 @@ class UpdatesReceiver : BroadcastReceiver() {
setEventUpdate(context, event) setEventUpdate(context, event)
} }
} else { } else {
val event = eventRepository.getEventByEventId(eventId) val event = eventRepository.getEventById(eventId)
if (event != null) { if (event != null) {
setEventUpdate(context, event) setEventUpdate(context, event)
} }
@ -161,18 +163,18 @@ class UpdatesReceiver : BroadcastReceiver() {
add(Calendar.DATE, 1) add(Calendar.DATE, 1)
}.timeInMillis <= fireTime) return }.timeInMillis <= fireTime) return
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact( setExactIfCanSchedule(
AlarmManager.RTC, AlarmManager.RTC,
fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60), fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60),
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
context, context,
event.eventID.toInt(), event.id.toInt(),
Intent(context, UpdatesReceiver::class.java).apply { Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE action = Actions.ACTION_TIME_UPDATE
if (event.startDate > now.timeInMillis) if (event.startDate > now.timeInMillis)
putExtra(EVENT_ID, event.eventID) putExtra(EVENT_ID, event.id)
}, },
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
) )
} }
@ -182,12 +184,12 @@ class UpdatesReceiver : BroadcastReceiver() {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply { cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE action = Actions.ACTION_CALENDAR_UPDATE
}, 0)) }, PendingIntent.FLAG_IMMUTABLE))
val eventRepository = EventRepository(context) val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach { eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java).apply { cancel(PendingIntent.getBroadcast(context, it.id.toInt(), Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE action = Actions.ACTION_TIME_UPDATE
}, 0)) }, PendingIntent.FLAG_IMMUTABLE))
} }
eventRepository.close() eventRepository.close()
} }

View File

@ -1,18 +1,12 @@
package com.tommasoberlose.anotherwidget.receivers package com.tommasoberlose.anotherwidget.receivers
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.tommasoberlose.anotherwidget.global.Actions import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import kotlinx.coroutines.Dispatchers import com.tommasoberlose.anotherwidget.services.WeatherWorker
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
class WeatherReceiver : BroadcastReceiver() { class WeatherReceiver : BroadcastReceiver() {
@ -23,57 +17,21 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED, Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED, Intent.ACTION_TIME_CHANGED,
Actions.ACTION_WEATHER_UPDATE -> setUpdates(context) Actions.ACTION_WEATHER_UPDATE -> {
}
}
companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) {
if (Preferences.showWeather) {
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) {
setExact(
AlarmManager.RTC,
System.currentTimeMillis() + interval,
PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
GlobalScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(context) WeatherHelper.updateWeather(context)
} }
} }
} }
fun setOneTimeUpdate(context: Context) { companion object {
fun setUpdates(context: Context) {
if (Preferences.showWeather) { if (Preferences.showWeather) {
listOf(10, 20, 30).forEach { WeatherWorker.enqueueTrigger(context)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactAndAllowWhileIdle(
AlarmManager.RTC,
it * MINUTE,
PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
} }
} }
fun removeUpdates(context: Context) { fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) { WeatherWorker.cancelTrigger(context)
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
listOf(10, 20, 30).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
}
}
} }
} }
} }

View File

@ -24,7 +24,11 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
val uri = Uri.parse("https://www.google.com/search?q=weather") val uri = Uri.parse("https://yandex.ru/pogoda")
.buildUpon()
.appendQueryParameter("lat", Preferences.customLocationLat)
.appendQueryParameter("lon", Preferences.customLocationLon)
.build()
val i = Intent(Intent.ACTION_VIEW, uri) val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try { try {

View File

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

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

View File

@ -1,144 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.app.job.JobScheduler
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.IBinder
import android.util.Log
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
class LocationService : Service() {
private var job: Job? = null
override fun onCreate() {
super.onCreate()
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
if (checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
if (com.google.android.gms.common.GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this@LocationService)
== com.google.android.gms.common.ConnectionResult.SUCCESS
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation
} else {
val lm = getSystemService(LOCATION_SERVICE) as android.location.LocationManager
var location: android.location.Location? = null
for (provider in arrayOf(
"fused", // LocationManager.FUSED_PROVIDER,
android.location.LocationManager.GPS_PROVIDER,
android.location.LocationManager.NETWORK_PROVIDER,
android.location.LocationManager.PASSIVE_PROVIDER
)) {
if (lm.isProviderEnabled(provider)) {
location = lm.getLastKnownLocation(provider)
if (location != null) break
}
}
com.google.android.gms.tasks.Tasks.forResult(location)
}.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
stopSelf()
}
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
companion object {
const val LOCATION_ACCESS_NOTIFICATION_ID = 28465
@JvmStatic
fun requestNewLocation(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, LocationService::class.java))
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun getLocationAccessNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.location_access_notification_channel_id),
getString(R.string.location_access_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.location_access_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@LocationService, getString(R.string.location_access_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.location_access_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@LocationService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@LocationService, 0, Intent(this@LocationService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
overridePendingTransition(R.anim.nav_default_enter_anim, R.anim.nav_default_exit_anim)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java) viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
@ -140,7 +141,6 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) { if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
Preferences.showEvents = false Preferences.showEvents = false
com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver.removeUpdates(this)
} }
} }

View File

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

View File

@ -1,96 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities.settings
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode.OK
import com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivitySupportDevBinding
import com.tommasoberlose.anotherwidget.ui.viewmodels.settings.SupportDevViewModel
import com.tommasoberlose.anotherwidget.utils.toast
import net.idik.lib.slimadapter.SlimAdapter
class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var viewModel: SupportDevViewModel
private lateinit var adapter: SlimAdapter
private lateinit var binding: ActivitySupportDevBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(SupportDevViewModel::class.java)
viewModel.billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build()
binding = ActivitySupportDevBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<SkuDetails>(R.layout.inapp_product_layout) { item, injector ->
item.sku
injector
.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)
}
}
.attachTo(binding.listView)
viewModel.openConnection()
subscribeUi(viewModel)
binding.actionBack.setOnClickListener {
onBackPressed()
}
setContentView(binding.root)
}
private fun subscribeUi(viewModel: SupportDevViewModel) {
viewModel.products.observe(this, Observer {
if (it.isNotEmpty()) {
binding.loader.isVisible = false
}
adapter.updateData(it.sortedWith(compareBy(SkuDetails::getPriceAmountMicros)))
})
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult.responseCode == OK && purchases != null) {
for (purchase in purchases) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
viewModel.handlePurchase(purchase)
toast(getString(R.string.thanks))
}
}
} else if (billingResult.responseCode == USER_CANCELED) {
// DO nothing
} else {
toast(getString(R.string.error))
}
}
public override fun onDestroy() {
viewModel.closeConnection()
super.onDestroy()
}
}

View File

@ -169,6 +169,7 @@ class CustomDateActivity : AppCompatActivity() {
isDateCapitalize = viewModel.isDateCapitalize.value ?: true isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false isDateUppercase = viewModel.isDateUppercase.value ?: false
} }
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(this)
super.onBackPressed() super.onBackPressed()
} }

View File

@ -57,9 +57,7 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem() updateListItem()
binding.loader.isVisible = true binding.loader.isVisible = true
lifecycleScope.launch { WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
} }
.clicked(R.id.radioButton) { .clicked(R.id.radioButton) {
if (Preferences.weatherProvider != provider.rawValue) { if (Preferences.weatherProvider != provider.rawValue) {
@ -72,9 +70,7 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem() updateListItem()
binding.loader.isVisible = true binding.loader.isVisible = true
lifecycleScope.launch { WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
} }
.checked(R.id.radioButton, provider.rawValue == Preferences.weatherProvider) .checked(R.id.radioButton, provider.rawValue == Preferences.weatherProvider)
.with<TextView>(R.id.text2) { .with<TextView>(R.id.text2) {
@ -92,10 +88,8 @@ class WeatherProviderActivity : AppCompatActivity() {
} }
.clicked(R.id.action_configure) { .clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) { BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
binding.loader.isVisible = true binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity) WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}
}.show() }.show()
} }
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.rawValue == Preferences.weatherProvider) View.VISIBLE else View.GONE) .visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.rawValue == Preferences.weatherProvider) View.VISIBLE else View.GONE)
@ -115,8 +109,6 @@ class WeatherProviderActivity : AppCompatActivity() {
adapter.updateData( adapter.updateData(
Constants.WeatherProvider.values().asList() Constants.WeatherProvider.values().asList()
.filter { it != Constants.WeatherProvider.HERE }
.filter { it != Constants.WeatherProvider.ACCUWEATHER }
) )
setupListener() setupListener()
@ -165,7 +157,7 @@ class WeatherProviderActivity : AppCompatActivity() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: MainFragment.UpdateUiMessageEvent?) { fun onMessageEvent(@Suppress("UNUSED_PARAMETER") ignore: MainFragment.UpdateUiMessageEvent?) {
binding.loader.isVisible = Preferences.weatherProviderError == "-" binding.loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") { if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(binding.listView, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show() Snackbar.make(binding.listView, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()

View File

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -86,6 +87,7 @@ class MainFragment : Fragment() {
binding.actionSettings.isClickable = !show binding.actionSettings.isClickable = !show
binding.actionSettings.isFocusable = !show binding.actionSettings.isFocusable = !show
binding.fragmentTitle.text = if (show) destination.label.toString() else getString(R.string.app_name) binding.fragmentTitle.text = if (show) destination.label.toString() else getString(R.string.app_name)
binding.toolbar.cardElevation = 0f
} }
binding.actionSettings.setOnSingleClickListener { binding.actionSettings.setOnSingleClickListener {
@ -138,7 +140,11 @@ class MainFragment : Fragment() {
} }
viewModel.fragmentScrollY.observe(viewLifecycleOwner) { viewModel.fragmentScrollY.observe(viewLifecycleOwner) {
binding.toolbar.cardElevation = if (it > 0) 24f else 0f binding.toolbar.cardElevation = if (it > 0) 32f else 0f
}
viewModel.showPreview.observe(viewLifecycleOwner) {
binding.preview.isVisible = it
} }
viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) { viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) {
@ -153,23 +159,24 @@ class MainFragment : Fragment() {
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface -> WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob?.cancel() uiJob?.cancel()
uiJob = lifecycleScope.launch(Dispatchers.IO) { uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.getWidgetView(requireContext(), binding.widget.width, typeface) val generatedView = MainWidget.getWidgetView(
requireContext(),
binding.widget.width - binding.widget.paddingStart - binding.widget.paddingEnd,
typeface
)
if (generatedView != null) { if (generatedView != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val view: View = generatedView.apply(requireActivity().applicationContext, binding.widget) val view: View = generatedView.apply(requireActivity().applicationContext, binding.widget)
view.measure(0, 0) view.measure(0, 0)
binding.widgetLoader.animate().scaleX(1f).scaleY(1f).alpha(1f) binding.widgetLoader.animate().alpha(0f).setDuration(200L).start()
.setDuration(200L).start()
binding.widget.animate().alpha(0f).setDuration(200L).withEndAction { binding.widget.animate().alpha(0f).setDuration(200L).withEndAction {
updatePreviewVisibility(view.measuredHeight)
binding.widget.removeAllViews() binding.widget.removeAllViews()
binding.widget.addView(view) binding.widget.addView(view)
updatePreviewVisibility(view.measuredHeight) binding.widget.animate().setStartDelay(300L).alpha(1f).start()
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.widget.animate().alpha(1f).start()
}.start() }.start()
} }
} }
@ -179,6 +186,7 @@ class MainFragment : Fragment() {
} }
private fun updatePreviewVisibility(widgetHeight: Int) { private fun updatePreviewVisibility(widgetHeight: Int) {
if (isAdded) {
val newHeight = widgetHeight + 32f.convertDpToPixel(requireContext()).toInt() val newHeight = widgetHeight + 32f.convertDpToPixel(requireContext()).toInt()
if (binding.preview.layoutParams.height != newHeight) { if (binding.preview.layoutParams.height != newHeight) {
binding.preview.clearAnimation() binding.preview.clearAnimation()
@ -186,7 +194,7 @@ class MainFragment : Fragment() {
binding.preview.height, binding.preview.height,
newHeight newHeight
).apply { ).apply {
duration = 500L duration = 300L
addUpdateListener { addUpdateListener {
val animatedValue = animatedValue as Int val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams val layoutParams = binding.preview.layoutParams
@ -196,11 +204,12 @@ class MainFragment : Fragment() {
}.start() }.start()
} }
} }
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
updateUI() // updateUI()
} }
override fun onPause() { override fun onPause() {
@ -214,7 +223,7 @@ class MainFragment : Fragment() {
class ChangeTabEvent(val page: Int) class ChangeTabEvent(val page: Int)
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onUpdateUiEvent(ignore: UpdateUiMessageEvent?) { fun onUpdateUiEvent(@Suppress("UNUSED_PARAMETER") ignore: UpdateUiMessageEvent?) {
delayJob?.cancel() delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) { delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(300) delay(300)
@ -225,7 +234,7 @@ class MainFragment : Fragment() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onChangeTabEvent(ignore: ChangeTabEvent) { fun onChangeTabEvent(@Suppress("UNUSED_PARAMETER") ignore: ChangeTabEvent) {
val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment? val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
navHost?.navController?.navigateUp() navHost?.navController?.navigateUp()
} }

View File

@ -8,13 +8,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.transition.TransitionInflater
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.MultiplePermissionsReport
@ -32,13 +29,10 @@ import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.activities.settings.IntegrationsActivity import com.tommasoberlose.anotherwidget.ui.activities.settings.IntegrationsActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.settings.SupportDevActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import com.tommasoberlose.anotherwidget.utils.openURI import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.setOnSingleClickListener import com.tommasoberlose.anotherwidget.utils.setOnSingleClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -197,16 +191,11 @@ class SettingsFragment : Fragment() {
requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md") requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md")
} }
binding.actionHelpDev.setOnClickListener {
startActivity(Intent(requireContext(), SupportDevActivity::class.java))
}
binding.actionRefreshWidget.setOnClickListener { binding.actionRefreshWidget.setOnClickListener {
binding.actionRefreshIcon binding.actionRefreshIcon
.animate() .animate()
.rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f) .rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
.withEndAction { .withEndAction {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
try { try {
WeatherHelper.updateWeather(requireContext()) WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext()) CalendarHelper.updateEventList(requireContext())
@ -216,7 +205,6 @@ class SettingsFragment : Fragment() {
ex.printStackTrace() ex.printStackTrace()
} }
} }
}
.start() .start()
} }
} }

View File

@ -236,6 +236,7 @@ class CalendarFragment : Fragment() {
binding.showDiffTimeToggle.setOnCheckedChangeListener { _, isChecked -> binding.showDiffTimeToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showDiffTime = isChecked Preferences.showDiffTime = isChecked
updateCalendar()
} }
binding.actionShowNextEventOnMultipleLines.setOnClickListener { binding.actionShowNextEventOnMultipleLines.setOnClickListener {

View File

@ -146,7 +146,7 @@ class ClockFragment : Fragment() {
binding.actionClockTextSize.setOnClickListener { binding.actionClockTextSize.setOnClickListener {
BottomSheetPicker( BottomSheetPicker(
requireContext(), requireContext(),
items = (46 downTo 12).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) }, items = (120 downTo 30).filter { it % 2 == 0 }.map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.clockTextSize }, getSelected = { Preferences.clockTextSize },
header = getString(R.string.settings_clock_text_size_title), header = getString(R.string.settings_clock_text_size_title),
onItemSelected = {value -> onItemSelected = {value ->

View File

@ -144,6 +144,12 @@ class GesturesFragment : Fragment() {
binding.showMultipleEventsToggle.setOnCheckedChangeListener { _, isChecked -> binding.showMultipleEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showNextEvent = isChecked Preferences.showNextEvent = isChecked
if (!isChecked) {
com.tommasoberlose.anotherwidget.db.EventRepository(requireContext()).run {
resetNextEventData()
close()
}
}
} }
binding.actionOpenEventDetails.setOnClickListener { binding.actionOpenEventDetails.setOnClickListener {

View File

@ -8,26 +8,20 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.graphics.Canvas import android.graphics.Canvas
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.view.animation.LayoutAnimationController
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
@ -41,8 +35,6 @@ import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.models.GlanceProvider import com.tommasoberlose.anotherwidget.models.GlanceProvider
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver.Companion.FITNESS_OPTIONS
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.* import com.tommasoberlose.anotherwidget.utils.*
@ -238,40 +230,6 @@ class GlanceTabFragment : Fragment() {
injector.visibility(R.id.info_icon, View.VISIBLE) injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.customNotes != "" isVisible = Preferences.customNotes != ""
} }
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(
context
)
if (GoogleSignIn.hasPermissions(
account,
FITNESS_OPTIONS
) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION
))
) {
injector.text(
R.id.label,
if (Preferences.showDailySteps) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showDailySteps
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.text(R.id.label, getString(R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = false
}
}
Constants.GlanceProviderId.EVENTS -> { Constants.GlanceProviderId.EVENTS -> {
isVisible = isVisible =
Preferences.showEventsAsGlanceProvider Preferences.showEventsAsGlanceProvider
@ -491,30 +449,6 @@ class GlanceTabFragment : Fragment() {
Preferences.showDailySteps = false Preferences.showDailySteps = false
} }
if (dialog != null) {
dialog?.show()
}
}
2 -> {
try {
val account: GoogleSignInAccount? = GoogleSignIn.getSignedInAccountFromIntent(
data
).getResult(ApiException::class.java)
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
GoogleSignIn.requestPermissions(
requireActivity(),
1,
account,
FITNESS_OPTIONS
)
} else {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
} catch (e: ApiException) {
e.printStackTrace()
Preferences.showDailySteps = false
}
if (dialog != null) { if (dialog != null) {
dialog?.show() dialog?.show()
} }

View File

@ -23,6 +23,7 @@ import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentPreferencesBinding import com.tommasoberlose.anotherwidget.databinding.FragmentPreferencesBinding
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
@ -88,6 +89,7 @@ class PreferencesFragment : Fragment() {
CalendarHelper.setEventUpdatesAndroidN(requireContext()) CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else { } else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext()) CalendarHelper.removeEventUpdatesAndroidN(requireContext())
UpdatesReceiver.removeUpdates(requireContext())
} }
} }
} }
@ -124,7 +126,6 @@ class PreferencesFragment : Fragment() {
requireCalendarPermission() requireCalendarPermission()
} else { } else {
Preferences.showEvents = enabled Preferences.showEvents = enabled
UpdatesReceiver.removeUpdates(requireContext())
} }
} }
@ -137,7 +138,7 @@ class PreferencesFragment : Fragment() {
if (enabled) { if (enabled) {
Preferences.weatherProviderError = "" Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = "" Preferences.weatherProviderLocationError = ""
WeatherReceiver.setUpdates(requireContext()) WeatherHelper.updateWeather(requireContext())
} else { } else {
WeatherReceiver.removeUpdates(requireContext()) WeatherReceiver.removeUpdates(requireContext())
} }
@ -165,6 +166,7 @@ class PreferencesFragment : Fragment() {
.withPermissions( .withPermissions(
Manifest.permission.READ_CALENDAR Manifest.permission.READ_CALENDAR
).withListener(object: MultiplePermissionsListener { ).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let { report?.let {
val granted = report.areAllPermissionsGranted() val granted = report.areAllPermissionsGranted()
@ -172,12 +174,33 @@ class PreferencesFragment : Fragment() {
if (granted) { if (granted) {
CalendarHelper.updateEventList(requireContext()) CalendarHelper.updateEventList(requireContext())
} }
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_calendar),
getString(R.string.description_permission_calendar)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
android.content.Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
}
} }
} }
override fun onPermissionRationaleShouldBeShown( override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?, permissions: MutableList<PermissionRequest>?,
token: PermissionToken? token: PermissionToken?
) { ) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed // Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale. // or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest() token?.continuePermissionRequest()

View File

@ -317,6 +317,17 @@ class TypographyFragment : Fragment() {
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == android.app.Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code -> {
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(requireContext())
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) { private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false binding.scrollView.isScrollable = false
callback.invoke() callback.invoke()

View File

@ -134,23 +134,13 @@ class WeatherFragment : Fragment() {
} }
private fun checkLocationPermission() { private fun checkLocationPermission() {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) && if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
binding.locationPermissionAlert.isVisible = false binding.locationPermissionAlert.isVisible = false
} else if (Preferences.customLocationAdd == "") { } else if (Preferences.customLocationAdd == "") {
binding.locationPermissionAlert.isVisible = true binding.locationPermissionAlert.isVisible = true
binding.locationPermissionAlert.setOnClickListener { binding.locationPermissionAlert.setOnClickListener {
requirePermission() requirePermission()
} }
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)
) {
val text = getString(R.string.action_grant_permission) + " - " +
requireContext().packageManager.backgroundPermissionOptionLabel
binding.locationPermissionAlert.text = text
}
binding.weatherProviderLocationError.isVisible = false binding.weatherProviderLocationError.isVisible = false
} else { } else {
binding.locationPermissionAlert.isVisible = false binding.locationPermissionAlert.isVisible = false
@ -187,10 +177,8 @@ class WeatherFragment : Fragment() {
.addOnSelectItemListener { value -> .addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) { if (value != Preferences.weatherTempUnit) {
Preferences.weatherTempUnit = value Preferences.weatherTempUnit = value
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext()) WeatherHelper.updateWeather(requireContext())
} }
}
}.show() }.show()
} }
@ -218,10 +206,8 @@ class WeatherFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
when (requestCode) { when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> { Constants.RESULT_CODE_CUSTOM_LOCATION -> {
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext()) WeatherHelper.updateWeather(requireContext())
} }
}
//RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> { //RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
//} //}
} }
@ -232,19 +218,35 @@ class WeatherFragment : Fragment() {
private fun requirePermission() { private fun requirePermission() {
Dexter.withContext(requireContext()) Dexter.withContext(requireContext())
.withPermissions( .withPermissions(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R && Manifest.permission.ACCESS_FINE_LOCATION,
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) Manifest.permission.ACCESS_COARSE_LOCATION
Manifest.permission.ACCESS_BACKGROUND_LOCATION
else
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener { ).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) { override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let { report?.let {
if (report.areAllPermissionsGranted()) { if (report.grantedPermissionResponses.isNotEmpty()) {
checkLocationPermission() checkLocationPermission()
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext()) WeatherHelper.updateWeather(requireContext())
} }
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_location),
getString(R.string.description_permission_location)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
} }
} }
} }
@ -252,6 +254,7 @@ class WeatherFragment : Fragment() {
permissions: MutableList<PermissionRequest>?, permissions: MutableList<PermissionRequest>?,
token: PermissionToken? token: PermissionToken?
) { ) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed // Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale. // or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest() token?.continuePermissionRequest()
@ -260,6 +263,12 @@ class WeatherFragment : Fragment() {
.check() .check()
} }
override fun onResume() {
super.onResume()
checkWeatherProviderConfig()
checkLocationPermission()
}
private fun maintainScrollPosition(callback: () -> Unit) { private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false binding.scrollView.isScrollable = false
callback.invoke() callback.invoke()

View File

@ -135,7 +135,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true } addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true } addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true } addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetAlign)) { value = true } addSource(Preferences.asLiveData(Preferences::widgetAlign)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetMargin)) { value = true } addSource(Preferences.asLiveData(Preferences::widgetMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetPadding)) { value = true } addSource(Preferences.asLiveData(Preferences::widgetPadding)) { value = true }
@ -156,7 +155,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true } addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true } addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherTempUnit)) { value = true } addSource(Preferences.asLiveData(Preferences::weatherProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true } addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true } addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLon)) { value = true } addSource(Preferences.asLiveData(Preferences::customLocationLon)) { value = true }

View File

@ -1,69 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels.settings
import android.app.Activity
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.billingclient.api.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SupportDevViewModel : ViewModel() {
lateinit var billingClient: BillingClient
val products: MutableLiveData<List<SkuDetails>> = MutableLiveData(emptyList())
fun openConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val params = SkuDetailsParams.newBuilder()
params.setSkusList(listOf("donation_coffee", "donation_donuts", "donation_breakfast", "donation_lunch", "donation_dinner")).setType(BillingClient.SkuType.INAPP)
viewModelScope.launch(Dispatchers.IO) {
val skuDetailsList = billingClient.querySkuDetails(params.build()).skuDetailsList
withContext(Dispatchers.Main) {
products.value = skuDetailsList
}
}
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
fun purchase(activity: Activity, product: SkuDetails) {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(product)
.build()
billingClient.launchBillingFlow(activity, flowParams)
}
fun handlePurchase(purchase: Purchase) {
if (!purchase.isAcknowledged) {
viewModelScope.launch(Dispatchers.IO) {
val token = purchase.purchaseToken
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(token)
billingClient.acknowledgePurchase(acknowledgePurchaseParams.build())
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(token)
.build()
billingClient.consumePurchase(consumeParams)
}
}
}
fun closeConnection() {
billingClient.endConnection()
}
}

View File

@ -9,10 +9,9 @@ import androidx.lifecycle.viewModelScope
import com.koolio.library.DownloadableFontList import com.koolio.library.DownloadableFontList
import com.koolio.library.Font import com.koolio.library.Font
import com.koolio.library.FontList import com.koolio.library.FontList
import com.tommasoberlose.anotherwidget.BuildConfig //import com.tommasoberlose.anotherwidget.BuildConfig
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CustomFontViewModel(application: Application) : AndroidViewModel(application) { class CustomFontViewModel(application: Application) : AndroidViewModel(application) {
@ -34,7 +33,7 @@ class CustomFontViewModel(application: Application) : AndroidViewModel(applicati
} }
} }
DownloadableFontList.requestDownloadableFontList(fontListCallback, BuildConfig.GOOGLE_API_KEY) // DownloadableFontList.requestDownloadableFontList(fontListCallback, BuildConfig.GOOGLE_API_KEY)
} }
} }
} }

View File

@ -6,17 +6,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateMargins
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository import com.tommasoberlose.anotherwidget.db.EventRepository
@ -26,12 +22,12 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.* import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -60,7 +56,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
appWidgetId, appWidgetId,
IntentHelper.getWidgetUpdateIntent(context), IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent) views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
@ -69,7 +65,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding) views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
// Clock // Clock
@ -79,14 +74,14 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
try { try {
val generatedBinding = generateWidgetView(typeface) ?: return null val generatedBinding = generateWidgetView(typeface) ?: return null
val width = w - (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt() * 2
views.setImageViewBitmap( views.setImageViewBitmap(
R.id.bitmap_container, R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w) BitmapHelper.getBitmapFromView(generatedBinding.root, width)
) )
views = updateGridView(generatedBinding, views, appWidgetId) views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
return views return views
@ -106,7 +101,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
val i = Intent(context, WidgetClickListenerReceiver::class.java) val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0) val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent) views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent) views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
@ -136,7 +131,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getCalendarIntent(context), IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.date_rect, calPIntent) views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE) views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
@ -173,7 +168,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
NewCalendarEventReceiver::class.java NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT }, ).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
) )
@ -187,7 +182,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getEventIntent(context, nextEvent), IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent) views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setImageViewBitmap( views.setImageViewBitmap(
@ -224,7 +219,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address), IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else { } else {
@ -232,7 +227,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate), IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail) views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
} }
@ -241,10 +236,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE) views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, if (Preferences.showWeather && Preferences.weatherIcon != "") View.VISIBLE else View.GONE) views.setViewVisibility(R.id.weather_sub_line_rect, if (Preferences.showWeather && Preferences.weatherIcon != "") View.VISIBLE else View.GONE)
views.setViewVisibility(R.id.first_line_rect, View.GONE) views.setViewVisibility(R.id.first_line_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) { } else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false var showSomething = false
var isWeatherShown = false var isWeatherShown = false
@ -256,7 +247,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getMusicIntent(context), IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true showSomething = true
@ -271,7 +262,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getClockIntent(context), IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true showSomething = true
@ -287,7 +278,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getBatteryIntent(), IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true showSomething = true
@ -297,6 +288,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
} }
Constants.GlanceProviderId.CUSTOM_INFO -> { Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) { if (Preferences.customNotes.isNotEmpty()) {
showSomething = true
break@loop break@loop
} }
} }
@ -306,7 +298,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getFitIntent(context), IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true showSomething = true
@ -327,7 +319,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
widgetID, widgetID,
IntentHelper.getNotificationIntent(context), IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -355,7 +347,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
nextEvent, nextEvent,
forceEventDetails = true forceEventDetails = true
), ),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -369,7 +361,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") { if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java) val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0) val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -418,7 +410,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
) )
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
return views return views
@ -503,8 +494,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
now.timeInMillis, now.timeInMillis,
nextEvent.startDate nextEvent.startDate
) ).toLowerCase(Locale.getDefault())
.toLowerCase(Locale.getDefault())
} else { } else {
SettingsStringHelper.getAllDayEventDifferenceText( SettingsStringHelper.getAllDayEventDifferenceText(
context, context,
@ -517,7 +507,18 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
if (!Preferences.showNextEventOnMultipleLines) { if (!Preferences.showNextEventOnMultipleLines) {
bindingView.nextEventDifferenceTime.isVisible = true bindingView.nextEventDifferenceTime.isVisible = true
} else { } else {
bindingView.nextEvent.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime) val text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime)
if (text.endsWith(diffTime)) {
bindingView.nextEvent.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
(v as TextView).layout?.run {
val diff = diffTime.trimStart();
val diffStart = text.length - diff.length
if (getLineStart(lineCount - 1) > diffStart)
v.text = (text.substring(0, diffStart).trimEnd() + '\n' + diff)
}
}
}
bindingView.nextEvent.text = text
bindingView.nextEventDifferenceTime.isVisible = false bindingView.nextEventDifferenceTime.isVisible = false
} }
} else { } else {
@ -584,11 +585,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
} else { } else {
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate } val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get( bindingView.subLineText.text = if (now.after(start)) {
Calendar.DAY_OF_YEAR)) {
DateHelper.getDateText(context, start)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateHelper.getDateText(context, now) DateHelper.getDateText(context, now)
} else { } else {
DateHelper.getDateText(context, start) DateHelper.getDateText(context, start)
@ -601,9 +598,12 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
bindingView.subLine.isVisible = true bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = Preferences.showWeather && Preferences.weatherIcon != "" bindingView.weatherSubLine.isVisible = Preferences.showWeather && Preferences.weatherIcon != ""
bindingView.subLineTopMarginSmall.visibility = View.GONE bindingView.subLineTopMarginSmall.visibility =
bindingView.subLineTopMarginMedium.visibility = View.GONE if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility = View.GONE bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) { } else if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.subLineIcon.isVisible = true bindingView.subLineIcon.isVisible = true
var showSomething = false var showSomething = false
@ -720,8 +720,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context, context,
now.timeInMillis, now.timeInMillis,
nextEvent.startDate nextEvent.startDate
) ).toLowerCase(Locale.getDefault())
.toLowerCase(Locale.getDefault())
} else { } else {
SettingsStringHelper.getAllDayEventDifferenceText( SettingsStringHelper.getAllDayEventDifferenceText(
context, context,
@ -844,48 +843,54 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
bindingView.nextEvent to Preferences.textMainSize, bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize, bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize, bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2), bindingView.weatherSubLineDivider to (Preferences.textSecondSize * 0.9f),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize, bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach { ).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second) it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
if (!it.first.includeFontPadding && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P)
it.first.isFallbackLineSpacing = false
} }
// Icons scale // Icons scale
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f listOf(
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f bindingView.subLineIcon to Preferences.textSecondSize / 16f,
bindingView.subLineIconShadow to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f bindingView.weatherSubLineWeatherIcon to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f bindingView.weatherDateLineWeatherIcon to ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 24f,
bindingView.actionNext to Preferences.textMainSize / 24f,
bindingView.weatherDateLineWeatherIcon.scaleX = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f bindingView.actionNextShadow to Preferences.textMainSize / 24f
bindingView.weatherDateLineWeatherIcon.scaleY = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f ).forEach {
if (it.first.tag == null)
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f it.first.tag = it.first.layoutParams.height
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f it.first.layoutParams = it.first.layoutParams.apply {
height = ((it.first.tag as Int) * it.second).roundToInt()
width = height
}
}
// Shadows // Shadows
val shadowRadius = val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f 0 -> 0f
1 -> 5f 1 -> 2f
2 -> 5f 2 -> 3f
else -> 5f else -> 2f
} }.toPixel(context)
val shadowColor = val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT 0 -> Color.TRANSPARENT
1 -> R.color.black_50 1 -> Color.DKGRAY
2 -> Color.BLACK 2 -> Color.BLACK
else -> R.color.black_50 else -> Color.DKGRAY
} }
val shadowDy = val shadowOffset =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f 0 -> 0f
1 -> 0f 1 -> 0f
2 -> 1f 2 -> 0.5f
else -> 0f else -> 0f
} }.toPixel(context)
listOf<TextView>( listOf<TextView>(
bindingView.date, bindingView.date,
@ -896,7 +901,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
bindingView.weatherSubLineDivider, bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature, bindingView.weatherSubLineTemperature,
).forEach { ).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor) it.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor)
} }
// Icons shadow // Icons shadow
@ -910,7 +915,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
it.second.isVisible = it.first.isVisible it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first) it.second.applyShadow(it.first, 0.8f)
} }
} }
@ -968,10 +973,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
// Dividers // Dividers
arrayOf(bindingView.weatherSubLineDivider).forEach { arrayOf(bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE it.visibility = if (Preferences.showDividers) View.VISIBLE else View.GONE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
} }
// Right Aligned // Right Aligned
@ -981,14 +983,16 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
} }
bindingView.mainContent.gravity = Gravity.END bindingView.mainContent.gravity = Gravity.END
bindingView.dateLayout.gravity = Gravity.END bindingView.dateLayout.gravity = Gravity.END
bindingView.date.gravity = Gravity.END
bindingView.calendarLayout.gravity = Gravity.END or Gravity.CENTER_VERTICAL bindingView.calendarLayout.gravity = Gravity.END or Gravity.CENTER_VERTICAL
bindingView.nextEvent.gravity = Gravity.END
bindingView.subLineContainer.gravity = Gravity.END or Gravity.CENTER_VERTICAL bindingView.subLineContainer.gravity = Gravity.END or Gravity.CENTER_VERTICAL
bindingView.subLineText.gravity = Gravity.END
} }
return bindingView return bindingView
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null return null
} }
} }

View File

@ -3,7 +3,6 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import com.tommasoberlose.anotherwidget.R import com.tommasoberlose.anotherwidget.R
@ -11,9 +10,7 @@ import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.utils.isDarkTheme import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
class ClockWidget(val context: Context) { class ClockWidget(val context: Context) {
fun updateClockView(views: RemoteViews, widgetID: Int): RemoteViews { fun updateClockView(views: RemoteViews, widgetID: Int): RemoteViews {
@ -32,18 +29,18 @@ class ClockWidget(val context: Context) {
views.setTextViewTextSize( views.setTextViewTextSize(
R.id.time, R.id.time,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) Preferences.clockTextSize
) )
views.setTextViewTextSize( views.setTextViewTextSize(
R.id.time_am_pm, R.id.time_am_pm,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 5 * 2 Preferences.clockTextSize / 5 * 2
) )
val clockPIntent = IntentHelper.getPendingIntent( val clockPIntent = IntentHelper.getPendingIntent(
context, context,
widgetID, widgetID,
IntentHelper.getClockIntent(context), IntentHelper.getClockIntent(context),
0 PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.time, clockPIntent) views.setOnClickPendingIntent(R.id.time, clockPIntent)
views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent) views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent)
@ -80,19 +77,29 @@ class ClockWidget(val context: Context) {
views.setTextViewTextSize( views.setTextViewTextSize(
R.id.alt_timezone_time, R.id.alt_timezone_time,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(context) / 3 Preferences.clockTextSize / 3
) )
views.setTextViewTextSize( views.setTextViewTextSize(
R.id.alt_timezone_time_am_pm, R.id.alt_timezone_time_am_pm,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2 (Preferences.clockTextSize / 3) / 5 * 2
) )
views.setTextViewTextSize( views.setTextViewTextSize(
R.id.alt_timezone_label, R.id.alt_timezone_label,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2 (Preferences.clockTextSize / 3) / 5 * 2
) )
val padding = (TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize,
context.resources.displayMetrics
) * 0.2).toInt()
if (Preferences.widgetAlign == Constants.WidgetAlign.RIGHT.rawValue)
views.setViewPadding(R.id.timezones_container, 0, padding, padding, 0)
else
views.setViewPadding(R.id.timezones_container, padding, padding, 0,0)
views.setOnClickPendingIntent(R.id.timezones_container, clockPIntent) views.setOnClickPendingIntent(R.id.timezones_container, clockPIntent)
views.setViewVisibility(R.id.timezones_container, View.VISIBLE) views.setViewVisibility(R.id.timezones_container, View.VISIBLE)
} else { } else {
@ -101,7 +108,6 @@ class ClockWidget(val context: Context) {
} }
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
return views return views

View File

@ -32,27 +32,28 @@ class MainWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) { override fun onEnabled(context: Context) {
CalendarHelper.updateEventList(context) CalendarHelper.updateEventList(context)
WeatherReceiver.setUpdates(context) WeatherHelper.updateWeather(context)
MediaPlayerHelper.updatePlayingMediaInfo(context) MediaPlayerHelper.updatePlayingMediaInfo(context)
if (Preferences.showEvents) {
CalendarHelper.setEventUpdatesAndroidN(context)
} else {
CalendarHelper.removeEventUpdatesAndroidN(context)
}
} }
override fun onDisabled(context: Context) { override fun onDisabled(context: Context) {
if (getWidgetCount(context) == 0) { if (getWidgetCount(context) == 0) {
CalendarHelper.removeEventUpdatesAndroidN(context)
UpdatesReceiver.removeUpdates(context) UpdatesReceiver.removeUpdates(context)
WeatherReceiver.removeUpdates(context) WeatherReceiver.removeUpdates(context)
} }
} }
companion object { companion object {
private val handler by lazy { android.os.Handler(android.os.Looper.getMainLooper()) }
fun updateWidget(context: Context) { fun updateWidget(context: Context) {
handler.run {
removeCallbacksAndMessages(null)
postDelayed ({
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context)) context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
}, 100)
}
} }
fun getWidgetCount(context: Context): Int { fun getWidgetCount(context: Context): Int {
@ -63,17 +64,13 @@ class MainWidget : AppWidgetProvider() {
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager,
appWidgetId: Int) { appWidgetId: Int) {
val displayMetrics = Resources.getSystem().displayMetrics
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId) val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId)
WidgetHelper.runWithCustomTypeface(context) { WidgetHelper.runWithCustomTypeface(context) {
val views = when (Preferences.widgetAlign) { val views = when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it) Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(appWidgetId, dimensions.first, it)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(context, rightAligned = true).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it) Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(context, rightAligned = true).generateWidget(appWidgetId, dimensions.first, it)
else -> StandardWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it) else -> StandardWidget(context).generateWidget(appWidgetId, dimensions.first, it)
} }
try { try {
if (views != null) appWidgetManager.updateAppWidget(appWidgetId, views) if (views != null) appWidgetManager.updateAppWidget(appWidgetId, views)

View File

@ -2,18 +2,14 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest import android.Manifest
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.TextView import android.widget.TextView
@ -28,7 +24,6 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.* import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
@ -63,7 +58,7 @@ class StandardWidget(val context: Context) {
context, context,
appWidgetId, appWidgetId,
IntentHelper.getWidgetUpdateIntent(context), IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent) views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
@ -72,7 +67,6 @@ class StandardWidget(val context: Context) {
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding) views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
// Clock // Clock
@ -82,14 +76,14 @@ class StandardWidget(val context: Context) {
try { try {
val generatedBinding = generateWidgetView(typeface) ?: return null val generatedBinding = generateWidgetView(typeface) ?: return null
val width = w - (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt() * 2
views.setImageViewBitmap( views.setImageViewBitmap(
R.id.bitmap_container, R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w) BitmapHelper.getBitmapFromView(generatedBinding.root, width)
) )
views = updateGridView(generatedBinding, views, appWidgetId) views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
return views return views
@ -109,7 +103,7 @@ class StandardWidget(val context: Context) {
val i = Intent(context, WidgetClickListenerReceiver::class.java) val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0) val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent) views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent) views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
@ -139,7 +133,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getCalendarIntent(context), IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.date_rect, calPIntent) views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE) views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
@ -182,7 +176,7 @@ class StandardWidget(val context: Context) {
context, context,
NewCalendarEventReceiver::class.java NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT }, ).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
) )
@ -201,7 +195,7 @@ class StandardWidget(val context: Context) {
context, context,
NewCalendarEventReceiver::class.java NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT }, ).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
) )
@ -217,7 +211,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getEventIntent(context, nextEvent), IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent) views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE) views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
@ -250,7 +244,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address), IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else { } else {
@ -258,7 +252,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate), IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail) views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
} }
@ -284,7 +278,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getMusicIntent(context), IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true showSomething = true
@ -299,7 +293,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getClockIntent(context), IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true showSomething = true
@ -315,7 +309,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getBatteryIntent(), IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true showSomething = true
@ -325,6 +319,7 @@ class StandardWidget(val context: Context) {
} }
Constants.GlanceProviderId.CUSTOM_INFO -> { Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) { if (Preferences.customNotes.isNotEmpty()) {
showSomething = true
break@loop break@loop
} }
} }
@ -334,7 +329,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getFitIntent(context), IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent) views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true showSomething = true
@ -355,7 +350,7 @@ class StandardWidget(val context: Context) {
context, context,
widgetID, widgetID,
IntentHelper.getNotificationIntent(context), IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -383,7 +378,7 @@ class StandardWidget(val context: Context) {
nextEvent, nextEvent,
forceEventDetails = true forceEventDetails = true
), ),
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -397,7 +392,7 @@ class StandardWidget(val context: Context) {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") { if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java) val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0) val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent( views.setOnClickPendingIntent(
R.id.sub_line_rect, R.id.sub_line_rect,
@ -444,7 +439,6 @@ class StandardWidget(val context: Context) {
} }
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} }
return views return views
@ -532,8 +526,7 @@ class StandardWidget(val context: Context) {
context, context,
now.timeInMillis, now.timeInMillis,
nextEvent.startDate nextEvent.startDate
) ).toLowerCase(Locale.getDefault())
.toLowerCase(Locale.getDefault())
} else { } else {
SettingsStringHelper.getAllDayEventDifferenceText( SettingsStringHelper.getAllDayEventDifferenceText(
context, context,
@ -546,7 +539,18 @@ class StandardWidget(val context: Context) {
if (!Preferences.showNextEventOnMultipleLines) { if (!Preferences.showNextEventOnMultipleLines) {
bindingView.nextEventDifferenceTime.isVisible = true bindingView.nextEventDifferenceTime.isVisible = true
} else { } else {
bindingView.nextEvent.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime) val text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime)
if (text.endsWith(diffTime)) {
bindingView.nextEvent.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
(v as TextView).layout?.run {
val diff = diffTime.trimStart();
val diffStart = text.length - diff.length
if (getLineStart(lineCount - 1) > diffStart)
v.text = (text.substring(0, diffStart).trimEnd() + '\n' + diff)
}
}
}
bindingView.nextEvent.text = text
bindingView.nextEventDifferenceTime.isVisible = false bindingView.nextEventDifferenceTime.isVisible = false
} }
} else { } else {
@ -613,11 +617,7 @@ class StandardWidget(val context: Context) {
} else { } else {
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate } val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get( bindingView.subLineText.text = if (now.after(start)) {
Calendar.DAY_OF_YEAR)) {
DateHelper.getDateText(context, start)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateHelper.getDateText(context, now) DateHelper.getDateText(context, now)
} else { } else {
DateHelper.getDateText(context, start) DateHelper.getDateText(context, start)
@ -752,8 +752,7 @@ class StandardWidget(val context: Context) {
context, context,
now.timeInMillis, now.timeInMillis,
nextEvent.startDate nextEvent.startDate
) ).toLowerCase(Locale.getDefault())
.toLowerCase(Locale.getDefault())
} else { } else {
SettingsStringHelper.getAllDayEventDifferenceText( SettingsStringHelper.getAllDayEventDifferenceText(
context, context,
@ -874,56 +873,60 @@ class StandardWidget(val context: Context) {
// Text Size // Text Size
listOf<Pair<TextView, Float>>( listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize, bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineDivider to (Preferences.textMainSize - 2), bindingView.weatherDateLineDivider to (Preferences.textMainSize * 0.9f),
bindingView.weatherDateLineTemperature to Preferences.textMainSize, bindingView.weatherDateLineTemperature to Preferences.textMainSize,
bindingView.nextEvent to Preferences.textMainSize, bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize, bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize, bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2), bindingView.weatherSubLineDivider to (Preferences.textSecondSize * 0.9f),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize, bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach { ).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second) it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
if (!it.first.includeFontPadding && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P)
it.first.isFallbackLineSpacing = false
} }
// Icons scale // Icons scale
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f listOf(
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f bindingView.subLineIcon to Preferences.textSecondSize / 16f,
bindingView.subLineIconShadow to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f bindingView.weatherSubLineWeatherIcon to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f bindingView.weatherDateLineWeatherIcon to Preferences.textMainSize / 24f,
bindingView.actionNext to Preferences.textMainSize / 24f,
bindingView.weatherDateLineWeatherIcon.scaleX = Preferences.textMainSize / 18f bindingView.actionNextShadow to Preferences.textMainSize / 24f,
bindingView.weatherDateLineWeatherIcon.scaleY = Preferences.textMainSize / 18f bindingView.actionPrevious to Preferences.textMainSize / 24f,
bindingView.actionPreviousShadow to Preferences.textMainSize / 24f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f ).forEach {
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f if (it.first.tag == null)
it.first.tag = it.first.layoutParams.height
bindingView.actionPrevious.scaleX = Preferences.textMainSize / 28f it.first.layoutParams = it.first.layoutParams.apply {
bindingView.actionPrevious.scaleY = Preferences.textMainSize / 28f height = ((it.first.tag as Int) * it.second).roundToInt()
width = height
}
}
// Shadows // Shadows
val shadowRadius = val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f 0 -> 0f
1 -> 5f 1 -> 2f
2 -> 5f 2 -> 3f
else -> 5f else -> 2f
} }.toPixel(context)
val shadowColor = val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT 0 -> Color.TRANSPARENT
1 -> R.color.black_50 1 -> Color.DKGRAY
2 -> Color.BLACK 2 -> Color.BLACK
else -> R.color.black_50 else -> Color.DKGRAY
} }
val shadowDy = val shadowOffset =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) { when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f 0 -> 0f
1 -> 0f 1 -> 0f
2 -> 1f 2 -> 0.5f
else -> 0f else -> 0f
} }.toPixel(context)
listOf<TextView>( listOf<TextView>(
bindingView.date, bindingView.date,
@ -935,7 +938,7 @@ class StandardWidget(val context: Context) {
bindingView.weatherSubLineDivider, bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature, bindingView.weatherSubLineTemperature,
).forEach { ).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor) it.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor)
} }
// Icons shadow // Icons shadow
@ -949,7 +952,7 @@ class StandardWidget(val context: Context) {
it.second.isVisible = it.first.isVisible it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first) it.second.applyShadow(it.first, 0.8f)
} }
} }
@ -1013,10 +1016,7 @@ class StandardWidget(val context: Context) {
// Dividers // Dividers
arrayOf(bindingView.weatherDateLineDivider, bindingView.weatherSubLineDivider).forEach { arrayOf(bindingView.weatherDateLineDivider, bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE it.visibility = if (Preferences.showDividers) View.VISIBLE else View.GONE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
} }
@ -1024,7 +1024,6 @@ class StandardWidget(val context: Context) {
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null return null
} }
} }

View File

@ -214,6 +214,14 @@ fun Context.checkGrantedPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
} }
fun android.app.AlarmManager.setExactIfCanSchedule(type: Int, triggerAtMillis: Long, operation: android.app.PendingIntent) {
// uncomment the following check after bumping compileSdkVersion/targetSdkVersion to 31
//if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S || canScheduleExactAlarms())
setExact(type, triggerAtMillis, operation)
//else
// set(type, triggerAtMillis, operation)
}
fun Context.getCurrentWallpaper(): Drawable? = try { fun Context.getCurrentWallpaper(): Drawable? = try {
WallpaperManager.getInstance(this).drawable WallpaperManager.getInstance(this).drawable
} catch (e: Exception) { } catch (e: Exception) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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