From 502d5d5dd5b5cf8f7060b7edafcaba1c9f54cccf Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 12:01:02 +0200 Subject: [PATCH 01/21] Updated activities related to SplitInstall (issue #2) --- app/build.gradle | 4 ++-- .../activities/DynamicFeatureProgress.kt | 2 ++ .../handwashingreminder/activities/LauncherActivity.kt | 4 ++-- .../activities/base/SplitCompatBaseActivity.kt | 5 +++-- .../application/HandwashingApplication.kt | 2 +- .../javinator9889/handwashingreminder/emoji/EmojiConfig.kt | 5 ++++- secrets.properties | 6 ------ 7 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 secrets.properties diff --git a/app/build.gradle b/app/build.gradle index 25c689b..102cf31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,8 +42,8 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 107 - versionName "1.1.1-${gitCommitHash}" + versionCode 108 + versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt index 203aa7f..d5c785e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt @@ -27,6 +27,7 @@ import android.widget.Toast import com.google.android.play.core.ktx.bytesDownloaded import com.google.android.play.core.ktx.errorCode import com.google.android.play.core.ktx.totalBytesToDownload +import com.google.android.play.core.splitinstall.SplitInstallHelper import com.google.android.play.core.splitinstall.SplitInstallRequest import com.google.android.play.core.splitinstall.SplitInstallSessionState import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener @@ -163,6 +164,7 @@ class DynamicFeatureProgress : SplitCompatBaseActivity(), percentage.text = getString(R.string.installing) } SplitInstallSessionStatus.INSTALLED -> { + SplitInstallHelper.updateAppInfo(this) if (++currentModule >= moduleCount) { dynamic_content_title.text = getString(R.string.done) setResultWithIntent(Activity.RESULT_OK) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index f229aa9..c9026cb 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -189,10 +189,10 @@ class LauncherActivity : AppCompatActivity() { modules += AppIntro.MODULE_NAME launchOnInstall = true } - /*modules += if (isAtLeast(AndroidVersion.LOLLIPOP)) + modules += if (isAtLeast(AndroidVersion.LOLLIPOP)) OkHttp.MODULE_NAME else - OkHttpLegacy.MODULE_NAME*/ + OkHttpLegacy.MODULE_NAME if (googleApi.isGooglePlayServicesAvailable( this, GOOGLE_PLAY_SERVICES_MIN_VERSION diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/SplitCompatBaseActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/SplitCompatBaseActivity.kt index 3856c23..90f8850 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/SplitCompatBaseActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/SplitCompatBaseActivity.kt @@ -30,11 +30,12 @@ abstract class SplitCompatBaseActivity : BaseAppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - splitInstallManager = SplitInstallManagerFactory.create(this) + splitInstallManager = + SplitInstallManagerFactory.create(applicationContext) } override fun attachBaseContext(base: Context) { super.attachBaseContext(base) - SplitCompat.install(this) + SplitCompat.install(base) } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt index 4929eaf..adbd173 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -56,7 +56,7 @@ class HandwashingApplication : BaseApplication() { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) MultiDex.install(base) - SplitCompat.install(this) + SplitCompat.install(base) } /** diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiConfig.kt b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiConfig.kt index 3a8136d..cf4c64b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiConfig.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiConfig.kt @@ -36,7 +36,10 @@ object EmojiConfig { context, GOOGLE_PLAY_SERVICES_MIN_VERSION ) == ConnectionResult.SUCCESS || - !isModuleInstalled(context, BundledEmoji.MODULE_NAME) + !isModuleInstalled( + context.applicationContext, + BundledEmoji.MODULE_NAME + ) ) { with( FontRequest( diff --git a/secrets.properties b/secrets.properties deleted file mode 100644 index 1958fa6..0000000 --- a/secrets.properties +++ /dev/null @@ -1,6 +0,0 @@ -# (optional) Project id from Google Developer Console, https://console.developers.google.com/ -google_project_id="api-5245190277294621651-718463" -# (optional) Settings for building a signed release apk -signing_key_password="dhc9Xe7XoidlRo77hohh" -signing_keystore_password="dhc9Xe7XoidlRo77hohh" -signing_key_alias="key0" From ede0e19fbbaa91801a3b4130d684ba384a15f717 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 12:05:07 +0200 Subject: [PATCH 02/21] Moved OkHttpDownloader class into HttpDownloader --- app/build.gradle | 2 +- .../activities/views/viewmodels/VideoModel.kt | 2 +- .../network/HttpDownloader.kt | 33 ++++++++----- .../network/okhttp/OkHttpDownloader.kt | 49 ------------------- 4 files changed, 23 insertions(+), 63 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/network/okhttp/OkHttpDownloader.kt diff --git a/app/build.gradle b/app/build.gradle index 102cf31..abc7462 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,7 +89,7 @@ android { preDexLibraries true javaMaxHeapSize "1G" } - dynamicFeatures = [":appintro", ":ads", ":bundledemoji", ":okhttp", ":okhttplegacy"] + dynamicFeatures = [":appintro", ":ads", ":bundledemoji"] compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt index dbd3a77..dad24cd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt @@ -88,7 +88,7 @@ class VideoModel( file.createNewFile() val hashingSink = HashingSink.sha256(file.sink()) do { - val okHttpDownloader = HttpDownloader.newInstance() + val okHttpDownloader = HttpDownloader() with(okHttpDownloader.downloadFile(url)) { hashingSink.buffer().use { it.writeAll(this) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt index fb2eb75..ad4dced 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt @@ -18,18 +18,27 @@ */ package com.javinator9889.handwashingreminder.network -import com.javinator9889.handwashingreminder.network.okhttp.OkHttpDownloader as Downloader +import okhttp3.CacheControl +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.BufferedSource +import java.io.IOException -object HttpDownloader { - fun newInstance(): OkHttpDownloader { - /*val className = if (isAtLeast(AndroidVersion.LOLLIPOP)) - "${OkHttp.PACKAGE_NAME}.${OkHttp.CLASS_NAME}\$${OkHttp.PROVIDER_NAME}" - else - "${OkHttpLegacy.PACKAGE_NAME}.${OkHttpLegacy - .CLASS_NAME}\$${OkHttpLegacy.PROVIDER_NAME}" - val okHttpProvider = Class.forName(className).kotlin.objectInstance - as OkHttpDownloader.Provider - return okHttpProvider.newInstance()*/ - return Downloader.newInstance() +class HttpDownloader : OkHttpDownloader { + private val client = OkHttpClient() + + override fun downloadFile(url: String): BufferedSource { + val request = with(Request.Builder()) { + url(url) + cacheControl(CacheControl.FORCE_NETWORK) + build() + } + with(client.newCall(request).execute()) { + if (!isSuccessful) { + close() + throw IOException("Unexpected code $this") + } + return body()!!.source() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/network/okhttp/OkHttpDownloader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/network/okhttp/OkHttpDownloader.kt deleted file mode 100644 index 763497d..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/okhttp/OkHttpDownloader.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 21/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.network.okhttp - -import com.javinator9889.handwashingreminder.network.OkHttpDownloader -import okhttp3.CacheControl -import okhttp3.OkHttpClient -import okhttp3.Request -import okio.BufferedSource -import java.io.IOException - -class OkHttpDownloader : OkHttpDownloader { - private val client = OkHttpClient() - - companion object Provider : OkHttpDownloader.Provider { - override fun newInstance(): OkHttpDownloader = OkHttpDownloader() - } - - override fun downloadFile(url: String): BufferedSource { - val request = with(Request.Builder()) { - url(url) - cacheControl(CacheControl.FORCE_NETWORK) - build() - } - with(client.newCall(request).execute()) { - if (!isSuccessful) { - close() - throw IOException("Unexpected code $this") - } - return body()!!.source() - } - } -} \ No newline at end of file From 744fe24bd008dc2f4b6de70d6aa3fd5613ca505b Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 16:02:11 +0200 Subject: [PATCH 03/21] Using AlarmManager for handling notifications (issue #9) By using AlarmManager library, we can ensure that notifications will be delivered at specified time even when idle --- app/src/main/AndroidManifest.xml | 58 +++++---- .../activities/LauncherActivity.kt | 4 - .../gms/activity/ActivityReceiver.kt | 118 +++++++++--------- .../jobs/alarms/AlarmHandler.kt | 71 +++++++++++ .../jobs/alarms/AlarmReceiver.kt | 40 ++++++ .../handwashingreminder/jobs/alarms/Alarms.kt | 31 +++++ .../workers/BreakfastNotificationWorker.kt | 30 +++++ .../jobs/workers/DinnerNotificationWorker.kt | 30 +++++ .../jobs/workers/LunchNotificationWorker.kt | 30 +++++ .../workers/ScheduledNotificationWorker.kt | 95 ++++++++++++++ .../handwashingreminder/utils/Android.kt | 20 +++ .../handwashingreminder/utils/Time.kt | 39 ++++++ 12 files changed, 479 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastNotificationWorker.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerNotificationWorker.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchNotificationWorker.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a4984aa..cd19824 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,30 +24,36 @@ android:theme="@style/AppTheme" tools:ignore="LockedOrientationActivity"> - + - - - - - + + + + + - + android:exported="true" /> - + @@ -75,7 +79,15 @@ - + + + + , detectedActivity: Int, - context: Context, - coroutineScope: CoroutineScope = GlobalScope + context: Context ) { - val result = goAsync() - coroutineScope.launch { - try { - val notificationContent = when (detectedActivity) { - DetectedActivity.WALKING -> - NotificationContent( - R.string.activity_notification_walk, - R.string.activity_notification_walk_content - ) - DetectedActivity.RUNNING -> - NotificationContent( - R.string.activity_notification_run, - R.string.activity_notifications_run_content - ) - DetectedActivity.ON_BICYCLE -> - NotificationContent( - R.string.activity_notification_cycling, - R.string.activity_notification_cycling_content - ) - DetectedActivity.IN_VEHICLE -> - NotificationContent( - R.string.activity_notification_vehicle, - R.string.activity_notification_vehicle_content - ) - else -> throw IllegalArgumentException( - "Activity not recognized" - ) - } - val emojiCompat = emojiLoader.await() - val title = emojiCompat.process( - context.getText(notificationContent.title) + val notificationContent = when (detectedActivity) { + DetectedActivity.WALKING -> + NotificationContent( + R.string.activity_notification_walk, + R.string.activity_notification_walk_content ) - val content = emojiCompat.process( - context.getText(notificationContent.content) + DetectedActivity.RUNNING -> + NotificationContent( + R.string.activity_notification_run, + R.string.activity_notifications_run_content ) - withContext(Dispatchers.Main) { - notificationsHandler.createNotification( - R.drawable.ic_stat_handwashing, - R.drawable.handwashing_app_logo, - title, - content, - longContent = content - ) - } - } finally { - result.finish() - } + DetectedActivity.ON_BICYCLE -> + NotificationContent( + R.string.activity_notification_cycling, + R.string.activity_notification_cycling_content + ) + DetectedActivity.IN_VEHICLE -> + NotificationContent( + R.string.activity_notification_vehicle, + R.string.activity_notification_vehicle_content + ) + else -> throw IllegalArgumentException( + "Activity not recognized" + ) + } + val emojiCompat = emojiLoader.await() + var title = context.getText(notificationContent.title) + var content = context.getText(notificationContent.content) + try { + title = emojiCompat.process(title) + content = emojiCompat.process(content) + } catch (_: IllegalStateException) { + } + withContext(Dispatchers.Main) { + notificationsHandler.createNotification( + iconDrawable = R.drawable.ic_stat_handwashing, + largeIcon = R.drawable.handwashing_app_logo, + title = title, + content = content, + longContent = content + ) } } +} - private data class NotificationContent( - @StringRes val title: Int, - @StringRes val content: Int - ) -} \ No newline at end of file +private data class NotificationContent( + @StringRes val title: Int, + @StringRes val content: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt new file mode 100644 index 0000000..a03890e --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -0,0 +1,71 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.alarms + +import android.app.AlarmManager +import android.app.AlarmManager.RTC_WAKEUP +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.annotation.IntRange +import androidx.core.app.AlarmManagerCompat +import androidx.preference.PreferenceManager +import com.javinator9889.handwashingreminder.utils.timeAt + +class AlarmHandler(private val context: Context) { + private val alarmManager = + context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + + fun scheduleAlarm(alarm: Alarms) { + val pendingIntent = createPendingIntentForAlarm(alarm) + val alarmTime = getTimeForAlarm(alarm) + val scheduleTime = timeAt(alarmTime.hour, alarmTime.minute) + AlarmManagerCompat.setExactAndAllowWhileIdle( + alarmManager, RTC_WAKEUP, scheduleTime, pendingIntent + ) + } + + fun cancelAlarm(alarm: Alarms) { + val pendingIntent = createPendingIntentForAlarm(alarm) + alarmManager.cancel(pendingIntent) + } + + private fun createPendingIntentForAlarm(alarm: Alarms): PendingIntent { + return with(Intent(context, AlarmReceiver::class.java)) { + identifier = alarm.identifier + PendingIntent.getBroadcast(context, alarm.code, this, 0) + } + } + + private fun getTimeForAlarm(alarm: Alarms): ScheduleTimeData { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val savedTime = preferences.getString(alarm.preferenceKey, "") + if (savedTime.isNullOrBlank()) + throw IllegalStateException("Time value cannot be null") + val splitTime = savedTime.split("") + val hour = Integer.parseInt(splitTime[0]) + val minute = Integer.parseInt(splitTime[1]) + return ScheduleTimeData(hour, minute) + } +} + +private data class ScheduleTimeData( + @IntRange(from = 0, to = 23) val hour: Int, + @IntRange(from = 0, to = 23) val minute: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt new file mode 100644 index 0000000..aacde30 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.alarms + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.javinator9889.handwashingreminder.jobs.workers.BreakfastNotificationWorker +import com.javinator9889.handwashingreminder.jobs.workers.DinnerNotificationWorker +import com.javinator9889.handwashingreminder.jobs.workers.LunchNotificationWorker +import com.javinator9889.handwashingreminder.utils.goAsync + +class AlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val worker = when (intent.identifier) { + Alarms.BREAKFAST_ALARM.identifier -> + BreakfastNotificationWorker(context) + Alarms.LUNCH_ALARM.identifier -> LunchNotificationWorker(context) + Alarms.DINNER_ALARM.identifier -> DinnerNotificationWorker(context) + else -> return + } + goAsync { worker.doWork() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt new file mode 100644 index 0000000..f9f1a3b --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.alarms + +import com.javinator9889.handwashingreminder.utils.Preferences + +enum class Alarms( + val identifier: String, + val code: Int, val + preferenceKey: String +) { + BREAKFAST_ALARM("alarms:breakfast", 0, Preferences.BREAKFAST_TIME), + LUNCH_ALARM("alarms:lunch", 1, Preferences.LUNCH_TIME), + DINNER_ALARM("alarms:dinner", 2, Preferences.DINNER_TIME) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastNotificationWorker.kt new file mode 100644 index 0000000..e10187d --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastNotificationWorker.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.workers + +import android.content.Context +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms + +class BreakfastNotificationWorker(context: Context) : + ScheduledNotificationWorker(context) { + override val alarm: Alarms = Alarms.BREAKFAST_ALARM + override val titleRes: Int = R.string.breakfast_title + override val contentsRes: Int = R.array.breakfast_comments +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerNotificationWorker.kt new file mode 100644 index 0000000..b706a26 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerNotificationWorker.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.workers + +import android.content.Context +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms + +class DinnerNotificationWorker(context: Context) : + ScheduledNotificationWorker(context) { + override val alarm: Alarms = Alarms.DINNER_ALARM + override val titleRes: Int = R.string.dinner_title + override val contentsRes: Int = R.array.dinner_comments +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchNotificationWorker.kt new file mode 100644 index 0000000..c75f2d0 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchNotificationWorker.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.workers + +import android.content.Context +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms + +class LunchNotificationWorker(context: Context) : + ScheduledNotificationWorker(context) { + override val alarm: Alarms = Alarms.LUNCH_ALARM + override val titleRes: Int = R.string.lunch_title + override val contentsRes: Int = R.array.lunch_comments +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt new file mode 100644 index 0000000..cc1b5c6 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt @@ -0,0 +1,95 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 23/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs.workers + +import android.content.Context +import androidx.annotation.ArrayRes +import androidx.annotation.StringRes +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms +import com.javinator9889.handwashingreminder.notifications.NotificationsHandler +import com.javinator9889.handwashingreminder.utils.TIME_CHANNEL_ID +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import timber.log.Timber + +abstract class ScheduledNotificationWorker(context: Context) { + protected val context: Context = context.applicationContext + protected abstract val alarm: Alarms + protected abstract val titleRes: Int + protected abstract val contentsRes: Int + + suspend fun doWork() = coroutineScope { + try { + val emojiLoader = EmojiLoader.get(context) + with(HandwashingApplication.getInstance()) { + // Don't use so much resources, wait at most two seconds until + // completions or continue with execution + withTimeoutOrNull(2_000L) { + firebaseInitDeferred.await() + } + } + val notificationsHandler = NotificationsHandler( + context = context, + channelId = TIME_CHANNEL_ID, + channelName = getString(R.string.time_notification_channel_name), + channelDesc = getString(R.string.time_notification_channel_desc) + ) + val emojiCompat = emojiLoader.await() + var title = getText(titleRes) + var content = + getStringArray(contentsRes).toList().random() as CharSequence + try { + title = emojiCompat.process(title) + content = emojiCompat.process(content) + } catch (_: IllegalStateException) { } + withContext(Dispatchers.Main) { + notificationsHandler.createNotification( + iconDrawable = R.drawable.ic_stat_handwashing, + largeIcon = R.drawable.handwashing_app_logo, + title = title, + content = content, + longContent = content + ) + } + } catch (e: Exception) { + Timber.e(e, "Unhandled exception on worker class") + // We don't want to keep using CPU at this time if the request + // fails so schedule next execution + } finally { + with(AlarmHandler(context)) { + scheduleAlarm(alarm) + } + } + } + + private fun getString(@StringRes resId: Int): String = + context.getString(resId) + + private fun getText(@StringRes resId: Int): CharSequence = + context.getText(resId) + + private fun getStringArray(@ArrayRes resId: Int): Array = + context.resources.getStringArray(resId) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt index 255e8ed..9491f55 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.utils import android.app.ActivityManager +import android.content.BroadcastReceiver import android.content.ContentResolver import android.content.Context import android.content.pm.ApplicationInfo @@ -31,6 +32,9 @@ import com.google.android.play.core.splitinstall.SplitInstallManager import com.google.android.play.core.splitinstall.SplitInstallManagerFactory import com.javinator9889.handwashingreminder.BuildConfig import com.javinator9889.handwashingreminder.application.HandwashingApplication +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch fun isAtLeast(version: AndroidVersion): Boolean = @@ -108,3 +112,19 @@ fun isModuleInstalled(context: Context, module: String): Boolean = fun isModuleInstalled(manager: SplitInstallManager, module: String): Boolean = module in manager.installedModules + +// https://github.com/romannurik/muzei/blob/master/extensions/src/main/java/com/google/android/apps/muzei/util/BroadcastReceiverExt.kt +fun BroadcastReceiver.goAsync( + coroutineScope: CoroutineScope = GlobalScope, + block: suspend () -> Unit +) { + val result = goAsync() + coroutineScope.launch { + try { + block() + } finally { + // Always call finish, even if the coroutine scope was cancelled + result.finish() + } + } +} diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt index 1bb51da..7486327 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt @@ -22,6 +22,7 @@ import androidx.annotation.IntRange import java.time.Duration import java.time.LocalDateTime import java.time.LocalTime +import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.* import kotlin.math.abs @@ -67,3 +68,41 @@ fun runAt( abs(alarm.timeInMillis - now.timeInMillis) } +fun timeAt( + @IntRange(from = 0, to = 23) hour: Int, + @IntRange(from = 0, to = 59) minute: Int +): Long = + if (isAtLeast(AndroidVersion.O)) { + // trigger at hour:minute + val alarmTime = LocalTime.of(hour, minute) + var now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + val nowTime = now.toLocalTime() + // check if is the same time or if today's time has passed so + // then schedule for next day + if (nowTime == alarmTime || nowTime.isAfter(alarmTime)) { + now = now.plusDays(1) + } + now = now + .withHour(alarmTime.hour) + .withMinute(alarmTime.minute) + now.toInstant(ZoneOffset.UTC).toEpochMilli() + } else { + // get now time and truncate it to minutes + val now = Calendar.getInstance() + .apply { timeInMillis = System.currentTimeMillis() } + // clone now time truncated to minutes and set the specified hour and + // minute in the new Calendar object + val alarm = (now.clone() as Calendar).apply { + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minute) + set(Calendar.SECOND, 0) + } + val nowTime = now.time + val alarmTime = alarm.time + // check if they are the same time or if today's time has passed so + // then schedule for next day + if (nowTime == alarmTime || nowTime.after(alarmTime)) { + alarm.add(Calendar.HOUR_OF_DAY, 24) + } + alarm.timeInMillis + } From bf14b784442bb3f2bca3fc9377007d36955d2126 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 16:26:17 +0200 Subject: [PATCH 04/21] Notifications-#9: migrated to AlarmHandler --- app/build.gradle | 2 +- .../activities/LauncherActivity.kt | 6 +++--- .../views/fragments/settings/SettingsView.kt | 4 ++++ .../fragments/settings/TimePickerPreference.kt | 9 +++++---- .../jobs/BootCompletedJob.kt | 18 ++++++------------ .../handwashingreminder/jobs/UpdateReceiver.kt | 16 +++++----------- .../jobs/alarms/AlarmHandler.kt | 12 +++++++++++- .../appintro/IntroActivity.kt | 7 ++++--- 8 files changed, 39 insertions(+), 35 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index abc7462..7fa8575 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 108 + versionCode 109 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index eaa4d6d..2b96c74 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -42,7 +42,7 @@ import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.gms.vendor.BillingService -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.* import com.javinator9889.handwashingreminder.utils.Preferences.Companion.ADS_ENABLED import com.javinator9889.handwashingreminder.utils.Preferences.Companion.APP_INIT_KEY @@ -256,8 +256,8 @@ class LauncherActivity : AppCompatActivity() { } Timber.d("Initializing Billing Service") app.billingService = BillingService(this) - with(WorkHandler(this)) { - enqueuePeriodicNotificationsWorker() + with(AlarmHandler(this)) { + scheduleAllAlarms() } Timber.d("Adding periodic notifications if not enqueued yet") Timber.d("Setting-up Firebase custom properties") diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt index 12a7dc0..ad9823c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt @@ -41,6 +41,7 @@ import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.gms.splitservice.SplitInstallService +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.listeners.OnPurchaseFinishedListener import com.javinator9889.handwashingreminder.utils.* import com.mikepenz.aboutlibraries.LibsBuilder @@ -285,6 +286,7 @@ class SettingsView : PreferenceFragmentCompat(), emojiCompat = emojiLoader.await() breakfast?.let { it.icon = icon(Ionicons.Icon.ion_coffee) + it.alarm = Alarms.BREAKFAST_ALARM try { it.title = emojiCompat.process(getText(R.string.breakfast_pref_title)) @@ -299,6 +301,7 @@ class SettingsView : PreferenceFragmentCompat(), } lunch?.let { it.icon = icon(Ionicons.Icon.ion_android_restaurant) + it.alarm = Alarms.LUNCH_ALARM try { it.title = emojiCompat.process(getText(R.string.lunch_pref_title)) @@ -313,6 +316,7 @@ class SettingsView : PreferenceFragmentCompat(), } dinner?.let { it.icon = icon(Ionicons.Icon.ion_ios_moon_outline) + it.alarm = Alarms.DINNER_ALARM try { it.title = emojiCompat.process(getText(R.string.dinner_pref_title)) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/TimePickerPreference.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/TimePickerPreference.kt index 6e9ba48..f265205 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/TimePickerPreference.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/TimePickerPreference.kt @@ -23,7 +23,8 @@ import android.content.Context import android.util.AttributeSet import android.widget.TimePicker import androidx.preference.EditTextPreference -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.utils.formatTime class TimePickerPreference : EditTextPreference, @@ -37,7 +38,7 @@ class TimePickerPreference : EditTextPreference, context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int ) : super(context, attrs, defStyleAttr, defStyleRes) - + lateinit var alarm: Alarms lateinit var summaryText: CharSequence private fun setSummary(hours: Int, minutes: Int): String { @@ -70,8 +71,8 @@ class TimePickerPreference : EditTextPreference, override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { val time = setSummary(hourOfDay, minute) text = time - with(WorkHandler(context)) { - enqueuePeriodicNotificationsWorker(true) + with(AlarmHandler(context)) { + scheduleAlarm(alarm) } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt index 69d0cdd..86c662e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt @@ -22,13 +22,13 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.javinator9889.handwashingreminder.application.HandwashingApplication -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.Preferences import timber.log.Timber class BootCompletedJob : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_BOOT_COMPLETED) { val app = HandwashingApplication.getInstance() val preferences = app.sharedPreferences if (preferences.getBoolean( @@ -39,15 +39,9 @@ class BootCompletedJob : BroadcastReceiver() { app.activityHandler.startTrackingActivity() else app.activityHandler.disableActivityTracker() - try { - Timber.d("Enqueuing notifications as the device has rebooted") - with(WorkHandler(requireNotNull(context))) { - enqueuePeriodicNotificationsWorker() - } - } catch (_: IllegalArgumentException) { - Timber.w( - "Context is null so notifications cannot be scheduled" - ) + Timber.d("Enqueuing notifications as the device has rebooted") + with(AlarmHandler(context)) { + scheduleAllAlarms() } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt index df52419..6627b93 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt @@ -21,21 +21,15 @@ package com.javinator9889.handwashingreminder.jobs import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import timber.log.Timber class UpdateReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == Intent.ACTION_MY_PACKAGE_REPLACED) { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) { Timber.d("Package updated so rescheduling jobs") - try { - with(WorkHandler(requireNotNull(context))) { - enqueuePeriodicNotificationsWorker(true) - } - } catch (_: IllegalArgumentException) { - Timber.w( - "Context is null so notifications cannot be rescheduled" - ) + with(AlarmHandler(context)) { + scheduleAllAlarms() } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt index a03890e..00f70e0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -41,11 +41,21 @@ class AlarmHandler(private val context: Context) { ) } + fun scheduleAllAlarms() { + for (alarm in Alarms.values()) + scheduleAlarm(alarm) + } + fun cancelAlarm(alarm: Alarms) { val pendingIntent = createPendingIntentForAlarm(alarm) alarmManager.cancel(pendingIntent) } + fun cancelAllAlarms() { + for (alarm in Alarms.values()) + cancelAlarm(alarm) + } + private fun createPendingIntentForAlarm(alarm: Alarms): PendingIntent { return with(Intent(context, AlarmReceiver::class.java)) { identifier = alarm.identifier @@ -58,7 +68,7 @@ class AlarmHandler(private val context: Context) { val savedTime = preferences.getString(alarm.preferenceKey, "") if (savedTime.isNullOrBlank()) throw IllegalStateException("Time value cannot be null") - val splitTime = savedTime.split("") + val splitTime = savedTime.split(":") val hour = Integer.parseInt(splitTime[0]) val minute = Integer.parseInt(splitTime[1]) return ScheduleTimeData(hour, minute) diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt index 9b4275f..d611cb4 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -53,7 +53,7 @@ import com.javinator9889.handwashingreminder.appintro.fragments.TimeConfigIntroF import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigViewHolder import com.javinator9889.handwashingreminder.appintro.utils.AnimatedResources import com.javinator9889.handwashingreminder.application.HandwashingApplication -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler +import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.listeners.ViewHolder import com.javinator9889.handwashingreminder.utils.* import kotlinx.android.synthetic.main.animated_intro.* @@ -181,9 +181,10 @@ class IntroActivity : AppIntro2(), app.activityHandler.startTrackingActivity() else app.activityHandler.disableActivityTracker() - with(WorkHandler(this)) { - enqueuePeriodicNotificationsWorker() + with(AlarmHandler(this)) { + scheduleAllAlarms() } + cacheDir.run { deleteRecursively() } val firebaseAnalytics = FirebaseAnalytics.getInstance(this) with(Bundle(2)) { putBoolean( From 269b8d9a5fb0ca38169e21d07e984fb2048af9bc Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 17:56:42 +0200 Subject: [PATCH 05/21] Notifications-#9: migrated to AlarmHandler and included traces for Firebase Performance The WorkManager was completely moved to AlarmHandler. After some tests it may be safely removed --- .../activities/LauncherActivity.kt | 6 ++++- .../settings/ActivityMultiSelectList.kt | 13 +++++++--- .../activities/views/viewmodels/VideoModel.kt | 7 ++--- .../jobs/alarms/AlarmHandler.kt | 6 +++++ .../workers/ScheduledNotificationWorker.kt | 21 ++++++++++----- .../handwashingreminder/utils/Android.kt | 12 +++++++++ .../handwashingreminder/utils/Time.kt | 26 ++++++++++--------- 7 files changed, 64 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 2b96c74..4c57aa7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -236,7 +236,11 @@ class LauncherActivity : AppCompatActivity() { } private suspend fun initVariables() { - app.firebaseInitDeferred.await() + // Wait at most 3 seconds for Firebase to initialize. Then continue + // with the app initialization + withTimeoutOrNull(3_000L) { + app.firebaseInitDeferred.await() + } Timber.d("Firebase initialized correctly") Timber.d("Initializing Iconics") Iconics.init(this) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt index b9fb6a6..374d356 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt @@ -48,11 +48,16 @@ class ActivityMultiSelectList : MultiSelectListPreference { override fun notifyChanged() { super.notifyChanged() - loadSummary() - if (!isFirstCall) + if (!isFirstCall) { + loadSummary() reloadActivityHandler() - else - isFirstCall = false + } + } + + override fun onSetInitialValue(defaultValue: Any?) { + super.onSetInitialValue(defaultValue) + isFirstCall = false + loadSummary() } private fun loadSummary() { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt index dad24cd..d025e8f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt @@ -22,7 +22,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData -import com.google.firebase.perf.metrics.AddTrace import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.network.HttpDownloader import com.javinator9889.handwashingreminder.utils.Videos.URI.FILENAME @@ -30,6 +29,7 @@ import com.javinator9889.handwashingreminder.utils.Videos.URI.HASH import com.javinator9889.handwashingreminder.utils.Videos.URI.URL import com.javinator9889.handwashingreminder.utils.Videos.URI.VideoList import com.javinator9889.handwashingreminder.utils.isConnected +import com.javinator9889.handwashingreminder.utils.trace import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext @@ -63,7 +63,9 @@ class VideoModel( val hash = video[HASH] val filename = video[FILENAME] if (url != null && hash != null && filename != null) { - file = downloadVideo(url, hash, filename) + file = trace("videoDownload") { + downloadVideo(url, hash, filename) + } } } var isVideoDownloaded = true @@ -74,7 +76,6 @@ class VideoModel( return file.name } - @AddTrace(name = "videoDownload") private suspend fun downloadVideo( url: String, hash: String, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt index 00f70e0..707f3f5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -27,21 +27,27 @@ import androidx.annotation.IntRange import androidx.core.app.AlarmManagerCompat import androidx.preference.PreferenceManager import com.javinator9889.handwashingreminder.utils.timeAt +import timber.log.Timber +import java.util.* class AlarmHandler(private val context: Context) { private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager fun scheduleAlarm(alarm: Alarms) { + cancelAlarm(alarm) val pendingIntent = createPendingIntentForAlarm(alarm) val alarmTime = getTimeForAlarm(alarm) val scheduleTime = timeAt(alarmTime.hour, alarmTime.minute) + Timber.d("Alarm $alarm scheduled at ${Date(scheduleTime)}") + Timber.d("Current time: ${Date(System.currentTimeMillis())}") AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, RTC_WAKEUP, scheduleTime, pendingIntent ) } fun scheduleAllAlarms() { + cancelAllAlarms() for (alarm in Alarms.values()) scheduleAlarm(alarm) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt index cc1b5c6..6335e8f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt @@ -42,14 +42,8 @@ abstract class ScheduledNotificationWorker(context: Context) { suspend fun doWork() = coroutineScope { try { + val startTime = System.currentTimeMillis() val emojiLoader = EmojiLoader.get(context) - with(HandwashingApplication.getInstance()) { - // Don't use so much resources, wait at most two seconds until - // completions or continue with execution - withTimeoutOrNull(2_000L) { - firebaseInitDeferred.await() - } - } val notificationsHandler = NotificationsHandler( context = context, channelId = TIME_CHANNEL_ID, @@ -73,7 +67,20 @@ abstract class ScheduledNotificationWorker(context: Context) { longContent = content ) } + Timber.d( + "Posting a notification took: ${System + .currentTimeMillis() - startTime}ms" + ) } catch (e: Exception) { + with(HandwashingApplication.getInstance()) { + // Don't use so much resources, wait at most half a second until + // Firebase initializes or continue with execution. + // Firebase is only needed for Timber (Crashlytics) so until + // here there is no need to wait + withTimeoutOrNull(500L) { + firebaseInitDeferred.await() + } + } Timber.e(e, "Unhandled exception on worker class") // We don't want to keep using CPU at this time if the request // fails so schedule next execution diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt index 9491f55..00a0701 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt @@ -30,6 +30,8 @@ import android.os.Build import androidx.annotation.AnyRes import com.google.android.play.core.splitinstall.SplitInstallManager import com.google.android.play.core.splitinstall.SplitInstallManagerFactory +import com.google.firebase.perf.FirebasePerformance +import com.google.firebase.perf.metrics.Trace import com.javinator9889.handwashingreminder.BuildConfig import com.javinator9889.handwashingreminder.application.HandwashingApplication import kotlinx.coroutines.CoroutineScope @@ -128,3 +130,13 @@ fun BroadcastReceiver.goAsync( } } } + +inline fun trace(name: String, block: (Trace) -> T): T { + with(FirebasePerformance.startTrace(name)) { + return try { + block(this) + } finally { + stop() + } + } +} diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt index 7486327..f49c0c6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt @@ -19,10 +19,7 @@ package com.javinator9889.handwashingreminder.utils import androidx.annotation.IntRange -import java.time.Duration -import java.time.LocalDateTime -import java.time.LocalTime -import java.time.ZoneOffset +import java.time.* import java.time.temporal.ChronoUnit import java.util.* import kotlin.math.abs @@ -49,10 +46,10 @@ fun runAt( .withMinute(alarmTime.minute) abs(Duration.between(LocalDateTime.now(), now).toMillis()) } else { - // get now time and truncate it to minutes + // get current time val now = Calendar.getInstance() - // clone now time truncated to minutes and set the specified hour and - // minute in the new Calendar object + // get again current time but truncate it to minutes and with the + // specified hour:minute provided val alarm = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) @@ -75,27 +72,32 @@ fun timeAt( if (isAtLeast(AndroidVersion.O)) { // trigger at hour:minute val alarmTime = LocalTime.of(hour, minute) - var now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + // obtain local date-time with fixed timezone (system default) + var now = LocalDateTime.now() + .atZone(ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.MINUTES) val nowTime = now.toLocalTime() // check if is the same time or if today's time has passed so // then schedule for next day if (nowTime == alarmTime || nowTime.isAfter(alarmTime)) { now = now.plusDays(1) } - now = now + val scheduledTime = now .withHour(alarmTime.hour) .withMinute(alarmTime.minute) - now.toInstant(ZoneOffset.UTC).toEpochMilli() + .withZoneSameInstant(ZoneOffset.UTC) + scheduledTime.toInstant().toEpochMilli() } else { - // get now time and truncate it to minutes + // get now time and set to system's millis val now = Calendar.getInstance() .apply { timeInMillis = System.currentTimeMillis() } - // clone now time truncated to minutes and set the specified hour and + // clone now calendar epoch time and set the specified hour and // minute in the new Calendar object val alarm = (now.clone() as Calendar).apply { set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) } val nowTime = now.time val alarmTime = alarm.time From 2025bfc9301450da41d8b2fb2a2529fe1bdf7a56 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 17:59:46 +0200 Subject: [PATCH 06/21] Code cleanup (issue #8) --- .../application/HandwashingApplication.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt index adbd173..a52efbc 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -20,7 +20,6 @@ package com.javinator9889.handwashingreminder.application import android.content.Context import android.content.SharedPreferences -import android.util.Log import androidx.multidex.MultiDex import androidx.preference.PreferenceManager import com.google.android.play.core.splitcompat.SplitCompat @@ -67,17 +66,7 @@ class HandwashingApplication : BaseApplication() { instance = this sharedPreferences = getCustomSharedPreferences(this) activityHandler = ActivityHandler(this) - /*if (isDebuggable()) { - Timber.plant(Timber.DebugTree()) - Timber.d("Application is in DEBUG mode") - with(FirebaseCrashlytics.getInstance()) { - setCrashlyticsCollectionEnabled(false) - } - } else { - Timber.plant(LogReportTree()) - }*/ firebaseInitDeferred = initFirebaseAppAsync() - Log.d("Application", "Deferred Firebase Instantiating") } private fun initFirebaseAppAsync(): Deferred { From 9aaa760770b8e11816c3dea7cf56906b21eb559c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 18:40:15 +0200 Subject: [PATCH 07/21] Updated ProGuard rules --- app/build.gradle | 2 +- app/proguard-rules.pro | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7fa8575..fe766d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 109 + versionCode 110 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4d1d5e2..1327192 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -83,3 +83,15 @@ # prevent Crashlytics obfuscation -keep class com.google.firebase.crashlytics.** { *; } -dontwarn com.google.firebase.crashlytics.** + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +# keep the specified class members from being removed or renamed +# only if the class is preserved +-keepclassmembers class android.content.Intent { + android.content.Intent setIdentifier(java.lang.String); + java.lang.String getIdentifier(); +} From e216983ed1ed64003b6263858e08d6eed8d3b8b9 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 18:40:15 +0200 Subject: [PATCH 08/21] Updated ProGuard rules and fixed minor bug in AlarmHandler --- app/build.gradle | 2 +- app/proguard-rules.pro | 8 +++++++ .../jobs/alarms/AlarmHandler.kt | 22 +++++++++++-------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7fa8575..73152f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 109 + versionCode 111 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4d1d5e2..b24305b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -83,3 +83,11 @@ # prevent Crashlytics obfuscation -keep class com.google.firebase.crashlytics.** { *; } -dontwarn com.google.firebase.crashlytics.** + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class android.content.Intent { android.content.Intent setIdentifier(java.lang.String); } +-keep class android.content.Intent { java.lang.String getIdentifier(); } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt index 707f3f5..ae99178 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -35,15 +35,19 @@ class AlarmHandler(private val context: Context) { context.getSystemService(Context.ALARM_SERVICE) as AlarmManager fun scheduleAlarm(alarm: Alarms) { - cancelAlarm(alarm) - val pendingIntent = createPendingIntentForAlarm(alarm) - val alarmTime = getTimeForAlarm(alarm) - val scheduleTime = timeAt(alarmTime.hour, alarmTime.minute) - Timber.d("Alarm $alarm scheduled at ${Date(scheduleTime)}") - Timber.d("Current time: ${Date(System.currentTimeMillis())}") - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, RTC_WAKEUP, scheduleTime, pendingIntent - ) + try { + cancelAlarm(alarm) + val pendingIntent = createPendingIntentForAlarm(alarm) + val alarmTime = getTimeForAlarm(alarm) + val scheduleTime = timeAt(alarmTime.hour, alarmTime.minute) + Timber.d("Alarm $alarm scheduled at ${Date(scheduleTime)}") + Timber.d("Current time: ${Date(System.currentTimeMillis())}") + AlarmManagerCompat.setExactAndAllowWhileIdle( + alarmManager, RTC_WAKEUP, scheduleTime, pendingIntent + ) + } catch (_: IllegalStateException) { + Timber.i("Time values are not initialized yet") + } } fun scheduleAllAlarms() { From 49789db047f0922ee4867624bcb6305856c746fe Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 19:18:17 +0200 Subject: [PATCH 09/21] Updated ProGuard rules and fixed minor bug in AlarmHandler --- tgs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tgs b/tgs index d2ddbad..e8104d2 160000 --- a/tgs +++ b/tgs @@ -1 +1 @@ -Subproject commit d2ddbad8db2e29a090122f2a7da50aaa2ff2c3d2 +Subproject commit e8104d29ea8b3c8c1e534bc7de6bfc1ba4c3b7bf From 6e3c901dbb62b8cec6caca794ad0597ec7050c3c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 23 Apr 2020 19:43:08 +0200 Subject: [PATCH 10/21] Intent backward compatibility (see https://developer.android.com/reference/android/content/Intent#setIdentifier(java.lang.String)) --- app/build.gradle | 2 +- app/proguard-rules.pro | 3 --- .../handwashingreminder/jobs/alarms/AlarmHandler.kt | 4 +++- .../handwashingreminder/jobs/alarms/AlarmReceiver.kt | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 73152f2..662255c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 111 + versionCode 112 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index b24305b..d890619 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -88,6 +88,3 @@ public static **[] values(); public static ** valueOf(java.lang.String); } - --keep class android.content.Intent { android.content.Intent setIdentifier(java.lang.String); } --keep class android.content.Intent { java.lang.String getIdentifier(); } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt index ae99178..727cefd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -30,6 +30,8 @@ import com.javinator9889.handwashingreminder.utils.timeAt import timber.log.Timber import java.util.* +internal const val IDENTIFIER = "intent:id" + class AlarmHandler(private val context: Context) { private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager @@ -68,7 +70,7 @@ class AlarmHandler(private val context: Context) { private fun createPendingIntentForAlarm(alarm: Alarms): PendingIntent { return with(Intent(context, AlarmReceiver::class.java)) { - identifier = alarm.identifier + putExtra(IDENTIFIER, alarm.identifier) PendingIntent.getBroadcast(context, alarm.code, this, 0) } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt index aacde30..70b6bfa 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt @@ -28,7 +28,7 @@ import com.javinator9889.handwashingreminder.utils.goAsync class AlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - val worker = when (intent.identifier) { + val worker = when (intent.getStringExtra(IDENTIFIER)) { Alarms.BREAKFAST_ALARM.identifier -> BreakfastNotificationWorker(context) Alarms.LUNCH_ALARM.identifier -> LunchNotificationWorker(context) From 2264a0141fbb5f8086547c526c522d653536e97a Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 24 Apr 2020 15:42:16 +0200 Subject: [PATCH 11/21] Resolved issue #9 --- app/build.gradle | 4 +- .../jobs/alarms/AlarmHandler.kt | 3 - .../handwashingreminder/jobs/alarms/Alarms.kt | 4 +- .../workers/AbstractNotificationsWorker.kt | 170 ------------------ .../jobs/workers/BreakfastWorker.kt | 36 ---- .../jobs/workers/DinnerWorker.kt | 35 ---- .../jobs/workers/LunchWorker.kt | 35 ---- .../jobs/workers/WorkHandler.kt | 124 ------------- 8 files changed, 3 insertions(+), 408 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/AbstractNotificationsWorker.kt delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastWorker.kt delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerWorker.kt delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchWorker.kt delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt diff --git a/app/build.gradle b/app/build.gradle index 662255c..a9862cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 112 + versionCode 113 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -151,8 +151,6 @@ dependencies { // https://firebase.google.com/docs/remote-config/use-config-android implementation 'com.google.firebase:firebase-config:19.1.3' implementation 'com.google.firebase:firebase-config-ktx:19.1.3' - // https://developer.android.com/jetpack/androidx/releases/work#declaring_dependencies - implementation 'androidx.work:work-runtime-ktx:2.3.4' // https://mvnrepository.com/artifact/androidx.emoji/emoji/1.0.0 implementation 'androidx.emoji:emoji:1.0.0' implementation 'androidx.emoji:emoji-appcompat:1.0.0' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt index 727cefd..e1c9da6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmHandler.kt @@ -28,7 +28,6 @@ import androidx.core.app.AlarmManagerCompat import androidx.preference.PreferenceManager import com.javinator9889.handwashingreminder.utils.timeAt import timber.log.Timber -import java.util.* internal const val IDENTIFIER = "intent:id" @@ -42,8 +41,6 @@ class AlarmHandler(private val context: Context) { val pendingIntent = createPendingIntentForAlarm(alarm) val alarmTime = getTimeForAlarm(alarm) val scheduleTime = timeAt(alarmTime.hour, alarmTime.minute) - Timber.d("Alarm $alarm scheduled at ${Date(scheduleTime)}") - Timber.d("Current time: ${Date(System.currentTimeMillis())}") AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, RTC_WAKEUP, scheduleTime, pendingIntent ) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt index f9f1a3b..4353aa2 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt @@ -22,8 +22,8 @@ import com.javinator9889.handwashingreminder.utils.Preferences enum class Alarms( val identifier: String, - val code: Int, val - preferenceKey: String + val code: Int, + val preferenceKey: String ) { BREAKFAST_ALARM("alarms:breakfast", 0, Preferences.BREAKFAST_TIME), LUNCH_ALARM("alarms:lunch", 1, Preferences.LUNCH_TIME), diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/AbstractNotificationsWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/AbstractNotificationsWorker.kt deleted file mode 100644 index 29f7136..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/AbstractNotificationsWorker.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 22/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.annotation.ArrayRes -import androidx.annotation.IntRange -import androidx.annotation.StringRes -import androidx.preference.PreferenceManager -import androidx.work.* -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.application.HandwashingApplication -import com.javinator9889.handwashingreminder.emoji.EmojiLoader -import com.javinator9889.handwashingreminder.notifications.NotificationsHandler -import com.javinator9889.handwashingreminder.utils.TIME_CHANNEL_ID -import com.javinator9889.handwashingreminder.utils.runAt -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import timber.log.Timber -import java.util.concurrent.TimeUnit - -data class WorkScheduleParams( - @IntRange(from = 0, to = 23) val hour: Int, - @IntRange(from = 0, to = 59) val minute: Int -) - -abstract class AbstractNotificationsWorker( - context: Context, - params: WorkerParameters -) : CoroutineWorker(context, params) { - protected var maxRetries = 5 - protected var shouldScheduleNext = true - protected var workConstraints = with(Constraints.Builder()) { - setRequiredNetworkType(NetworkType.NOT_REQUIRED) - setRequiresBatteryNotLow(false) - setRequiresCharging(false) - setRequiresDeviceIdle(false) - setRequiresStorageNotLow(false) - build() - } - protected abstract val clazz: Class - protected abstract val workUniqueName: String - protected abstract val preferencesKey: String - protected abstract val titleRes: Int - protected abstract val commentsRes: Int - protected val workParams: WorkScheduleParams - get() { - val preferences = - PreferenceManager.getDefaultSharedPreferences(applicationContext) - val time = preferences.getString(preferencesKey, "") - if (time == "" || time == null) - throw IllegalStateException("Time value cannot be null") - val splitTime = time.split(":") - val hour = Integer.parseInt(splitTime[0]) - val minute = Integer.parseInt(splitTime[1]) - return WorkScheduleParams(hour, minute) - } - - override suspend fun doWork(): Result = coroutineScope { - with(HandwashingApplication.getInstance()) { - withTimeoutOrNull(10_000L) { - firebaseInitDeferred.await() - } - } - shouldScheduleNext = true - var data: Data? = null - try { - data = work() - Result.success() - } catch (e: Exception) { - catchBlock(e) - } finally { - if (shouldScheduleNext) { - scheduleNext(data) - } - } - } - - protected open suspend fun work(): Data? { - val emojiLoader = EmojiLoader.get(applicationContext) - val notificationsHandler = NotificationsHandler( - context = applicationContext, - channelId = TIME_CHANNEL_ID, - channelName = getString(R.string.time_notification_channel_name), - channelDesc = getString(R.string.time_notification_channel_desc) - ) - val emojiCompat = emojiLoader.await() - var title: CharSequence - var content: CharSequence - try { - title = emojiCompat.process(getText(titleRes)) - content = emojiCompat.process( - getStringArray(commentsRes).toList().random() - ) - } catch (_: IllegalStateException) { - title = getText(titleRes) - content = getStringArray(commentsRes).toList().random() - } - withContext(Dispatchers.Main) { - notificationsHandler.createNotification( - iconDrawable = R.drawable.ic_stat_handwashing, - largeIcon = R.drawable.handwashing_app_logo, - title = title, - content = content, - longContent = content - ) - } - return null - } - - protected fun catchBlock(e: Exception): Result { - if (runAttemptCount >= maxRetries) { - Timber.d("Exceeded max attempts: $maxRetries") - return Result.failure() - } - return when (e.cause) { - is IllegalStateException -> { - Timber.w(e, "IllegalStateException on worker class") - shouldScheduleNext = false - Result.retry() - } - else -> { - Timber.e(e, "Uncaught exception on worker class") - Result.failure() - } - } - } - - protected fun scheduleNext(data: Data?) { - val workManager = WorkManager.getInstance(applicationContext) - val nextExecutionDelay = runAt(workParams.hour, workParams.minute) - Timber.d("Executing $workUniqueName in ${nextExecutionDelay / 1000} s") - val jobRequest = with(OneTimeWorkRequest.Builder(clazz)) { - data?.let { setInputData(it) } - setInitialDelay(nextExecutionDelay, TimeUnit.MILLISECONDS) - setConstraints(workConstraints) - build() - } - workManager.enqueueUniqueWork( - workUniqueName, ExistingWorkPolicy.REPLACE, jobRequest - ) - } - - private fun getString(@StringRes resId: Int): String = - applicationContext.getString(resId) - - private fun getText(@StringRes resId: Int): CharSequence = - applicationContext.getText(resId) - - private fun getStringArray(@ArrayRes resId: Int): Array = - applicationContext.resources.getStringArray(resId) -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastWorker.kt deleted file mode 100644 index 252fad2..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastWorker.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 22/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.utils.Preferences -import com.javinator9889.handwashingreminder.utils.Workers - -class BreakfastWorker(context: Context, params: WorkerParameters) : - AbstractNotificationsWorker(context, params) { - override val clazz: Class = - BreakfastWorker::class.java - override val workUniqueName: String = Workers.BREAKFAST_UUID - override val preferencesKey: String = Preferences.BREAKFAST_TIME - override val titleRes: Int = R.string.breakfast_title - override val commentsRes: Int = R.array.breakfast_comments -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerWorker.kt deleted file mode 100644 index 8465623..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerWorker.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 22/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.utils.Preferences -import com.javinator9889.handwashingreminder.utils.Workers - -class DinnerWorker(context: Context, params: WorkerParameters) : - AbstractNotificationsWorker(context, params) { - override val clazz: Class = DinnerWorker::class.java - override val workUniqueName: String = Workers.DINNER_UUID - override val preferencesKey: String = Preferences.DINNER_TIME - override val titleRes: Int = R.string.dinner_title - override val commentsRes: Int = R.array.dinner_comments -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchWorker.kt deleted file mode 100644 index 404c102..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchWorker.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 22/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerParameters -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.utils.Preferences -import com.javinator9889.handwashingreminder.utils.Workers - -class LunchWorker(context: Context, params: WorkerParameters) : - AbstractNotificationsWorker(context, params) { - override val clazz: Class = LunchWorker::class.java - override val workUniqueName: String = Workers.LUNCH_UUID - override val preferencesKey: String = Preferences.LUNCH_TIME - override val titleRes: Int = R.string.lunch_title - override val commentsRes: Int = R.array.lunch_comments -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt deleted file mode 100644 index b04539d..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 11/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.preference.PreferenceManager -import androidx.work.* -import com.javinator9889.handwashingreminder.utils.Preferences -import com.javinator9889.handwashingreminder.utils.Workers -import com.javinator9889.handwashingreminder.utils.runAt -import timber.log.Timber -import java.util.concurrent.TimeUnit - -data class Who( - val uuid: String, - val id: Int, - val clazz: Class -) - -class WorkHandler(private val context: Context) { - private val workManager: WorkManager - get() = WorkManager.getInstance(context) - - fun enqueuePeriodicNotificationsWorker(forceUpdate: Boolean = false) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - - val breakfastTime = - preferences.getString(Preferences.BREAKFAST_TIME, "")!! - val lunchTime = preferences.getString(Preferences.LUNCH_TIME, "")!! - val dinnerTime = preferences.getString(Preferences.DINNER_TIME, "")!! - if (breakfastTime == "" || lunchTime == "" || dinnerTime == "") { - Timber.e("The scheduled times are not initialized") - return - } - val times = arrayOf(breakfastTime, lunchTime, dinnerTime) - times.forEach { time -> - val splittedTime = time.split(":") - val hour = Integer.parseInt(splittedTime[0].trim()) - val minute = Integer.parseInt(splittedTime[1].trim()) - - val timeDiff = runAt(hour, minute) - val who = when (time) { - breakfastTime -> Who( - Workers.BREAKFAST_UUID, - Workers.BREAKFAST, - BreakfastWorker::class.java - ) - lunchTime -> Who( - Workers.LUNCH_UUID, - Workers.LUNCH, - LunchWorker::class.java - ) - dinnerTime -> Who( - Workers.DINNER_UUID, - Workers.DINNER, - DinnerWorker::class.java - ) - else -> { - Timber.e("Unmatched time: $time against $times") - return - } - } - Timber.i( - "Scheduled activity ${who.uuid} in $timeDiff ms" - ) - - val jobRequest = createJobRequest(timeDiff, who.clazz) - - val policy = if (forceUpdate) - ExistingWorkPolicy.REPLACE - else - ExistingWorkPolicy.KEEP - - with(workManager) { - enqueueUniqueWork( - who.uuid, - policy, - jobRequest - ) - } - } - } - - private fun createJobRequest( - initialDelayMillis: Long, - clazz: Class, - inputData: Data? = null - ): OneTimeWorkRequest { - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.NOT_REQUIRED) - .setRequiresBatteryNotLow(false) - .setRequiresCharging(false) - .setRequiresDeviceIdle(false) - .setRequiresStorageNotLow(false) - .build() - return with(OneTimeWorkRequest.Builder(clazz)) { - setInitialDelay(initialDelayMillis, TimeUnit.MILLISECONDS) - inputData?.let { setInputData(it) } - setConstraints(constraints) - setBackoffCriteria( - BackoffPolicy.EXPONENTIAL, - 15000L, - TimeUnit.MILLISECONDS - ) - build() - } - } -} \ No newline at end of file From 1f38ee944a47a9281ba7804b0c4a3000ee06116c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 24 Apr 2020 15:56:43 +0200 Subject: [PATCH 12/21] Possible LottiePerformance issue (#7) approach --- app/src/main/AndroidManifest.xml | 1 + .../graphics/LottieAdaptedPerformanceAnimationView.kt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cd19824..5803404 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" + android:hardwareAccelerated="true" tools:ignore="LockedOrientationActivity"> Date: Sun, 26 Apr 2020 14:49:19 +0200 Subject: [PATCH 13/21] Updated AppIntro for using FastAdapter for managing RecyclerView In addition, the required layout for landscape was created and functional. This commit affords issues #3, #1 and #8 --- ads/build.gradle | 16 -- app/build.gradle | 50 ++--- .../graphics/RecyclingImageView.java | 34 +++- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values/strings.xml | 4 +- appintro/build.gradle | 38 +--- appintro/src/main/AndroidManifest.xml | 12 +- .../appintro/IntroActivity.kt | 182 +++++++++--------- .../appintro/config/TimeConfigActivity.kt | 11 +- .../fragments/TimeConfigIntroFragment.kt | 178 ++++++++++------- .../appintro/timeconfig/TimeConfigAdapter.kt | 3 +- .../appintro/timeconfig/TimeConfigItem.kt | 90 +++++++++ .../layout-land/time_card_view_expanded.xml | 175 +++++++++++++++++ appintro/src/main/res/layout/slide_policy.xml | 1 + .../src/main/res/layout/time_card_view.xml | 2 - .../res/layout/time_card_view_expanded.xml | 11 +- bundledemoji/build.gradle | 10 +- okhttp/.gitignore | 1 - okhttp/build.gradle | 30 --- okhttp/src/main/AndroidManifest.xml | 13 -- .../okhttp/OkHttpDownloader.kt | 49 ----- okhttplegacy/.gitignore | 1 - okhttplegacy/build.gradle | 32 --- okhttplegacy/okhttp3.pro | 11 -- okhttplegacy/src/main/AndroidManifest.xml | 13 -- .../okhttplegacy/OkHttpDownloader.kt | 49 ----- settings.gradle | 2 - 28 files changed, 551 insertions(+), 473 deletions(-) create mode 100644 appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt create mode 100644 appintro/src/main/res/layout-land/time_card_view_expanded.xml delete mode 100644 okhttp/.gitignore delete mode 100644 okhttp/build.gradle delete mode 100644 okhttp/src/main/AndroidManifest.xml delete mode 100644 okhttp/src/main/java/com/javinator9889/handwashingreminder/okhttp/OkHttpDownloader.kt delete mode 100644 okhttplegacy/.gitignore delete mode 100644 okhttplegacy/build.gradle delete mode 100644 okhttplegacy/okhttp3.pro delete mode 100644 okhttplegacy/src/main/AndroidManifest.xml delete mode 100644 okhttplegacy/src/main/java/com/javinator9889/handwashingreminder/okhttplegacy/OkHttpDownloader.kt diff --git a/ads/build.gradle b/ads/build.gradle index 12eb5ec..1a212ef 100644 --- a/ads/build.gradle +++ b/ads/build.gradle @@ -27,25 +27,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':app') - implementation "androidx.core:core-ktx:1.2.0" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - // https://developer.android.com/kotlin/ktx#play-core - implementation 'com.google.android.play:core-ktx:1.7.0' // https://firebase.google.com/docs/admob/android/quick-start#import_the_mobile_ads_sdk implementation 'com.google.firebase:firebase-ads:19.1.0' - // https://developer.android.com/studio/build/multidex - implementation 'androidx.multidex:multidex:2.0.1' - // https://github.com/JakeWharton/timber - implementation 'com.jakewharton.timber:timber:4.7.1' - // https://firebase.google.com/docs/android/setup#add-sdks - implementation 'com.google.firebase:firebase-core:17.3.0' - implementation 'com.google.firebase:firebase-common-ktx:19.3.0' - implementation 'com.google.firebase:firebase-analytics:17.3.0' - implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' - implementation 'com.google.firebase:firebase-perf:19.0.6' - // https://developer.android.com/jetpack/androidx/releases/cardview - implementation 'androidx.cardview:cardview:1.0.0' } repositories { mavenCentral() diff --git a/app/build.gradle b/app/build.gradle index a9862cc..cf00ca9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,56 +104,58 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + api 'androidx.appcompat:appcompat:1.1.0' + api 'androidx.core:core-ktx:1.2.0' + api 'androidx.legacy:legacy-support-v4:1.0.0' + api 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' // https://github.com/Javinator9889/LocaleManager - implementation 'com.github.javinator9889:localemanager:1.1X' + api 'com.github.javinator9889:localemanager:1.1X' // https://material.io/develop/android/docs/getting-started/ - implementation 'com.google.android.material:material:1.1.0' + api 'com.google.android.material:material:1.1.0' // https://developers.google.com/android/guides/setup implementation 'com.google.android.gms:play-services-location:17.0.0' // https://developer.android.com/jetpack/androidx/releases/annotation - implementation 'androidx.annotation:annotation:1.1.0' + api 'androidx.annotation:annotation:1.1.0' // https://developer.android.com/jetpack/androidx/releases/cardview - implementation 'androidx.cardview:cardview:1.0.0' + api 'androidx.cardview:cardview:1.0.0' // https://developer.android.com/jetpack/androidx/releases/recyclerview - implementation 'androidx.recyclerview:recyclerview:1.1.0' + api 'androidx.recyclerview:recyclerview:1.1.0' // https://developer.android.com/studio/build/multidex - implementation 'androidx.multidex:multidex:2.0.1' + api 'androidx.multidex:multidex:2.0.1' // https://github.com/mikepenz/Android-Iconics - implementation 'com.mikepenz:iconics-core:5.0.2' - implementation 'com.mikepenz:iconics-views:5.0.2' + api 'com.mikepenz:iconics-core:5.0.2' + api 'com.mikepenz:iconics-views:5.0.2' //noinspection GradleDependency - implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' - implementation 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar' + api 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' + api 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar' // https://github.com/mikepenz/AboutLibraries implementation "com.mikepenz:aboutlibraries-core:${latestAboutLibsRelease}" implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" // https://developer.android.com/kotlin/ktx#play-core - implementation 'com.google.android.play:core-ktx:1.7.0' + api 'com.google.android.play:core:1.7.2' + api 'com.google.android.play:core-ktx:1.7.0' // https://developer.android.com/kotlin/ktx#collection implementation 'androidx.collection:collection-ktx:1.1.0' // https://kotlinlang.org/docs/reference/reflection.html implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // https://firebase.google.com/docs/android/setup#add-sdks - implementation 'com.google.firebase:firebase-common-ktx:19.3.0' - implementation 'com.google.firebase:firebase-analytics:17.3.0' - implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' - implementation 'com.google.firebase:firebase-perf:19.0.6' + api 'com.google.firebase:firebase-common-ktx:19.3.0' + api 'com.google.firebase:firebase-analytics:17.3.0' + api 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' + api 'com.google.firebase:firebase-perf:19.0.6' // http://airbnb.io/lottie/#/android?id=getting-started - implementation "com.airbnb.android:lottie:3.4.0" + api "com.airbnb.android:lottie:3.4.0" // https://firebase.google.com/docs/remote-config/use-config-android implementation 'com.google.firebase:firebase-config:19.1.3' implementation 'com.google.firebase:firebase-config-ktx:19.1.3' // https://mvnrepository.com/artifact/androidx.emoji/emoji/1.0.0 - implementation 'androidx.emoji:emoji:1.0.0' - implementation 'androidx.emoji:emoji-appcompat:1.0.0' + api 'androidx.emoji:emoji:1.0.0' + api 'androidx.emoji:emoji-appcompat:1.0.0' // https://github.com/mikepenz/FastAdapter implementation "com.mikepenz:fastadapter:${latestFastAdapterRelease}" // https://developer.android.com/kotlin/ktx#lifecycle @@ -165,13 +167,13 @@ dependencies { // https://developer.android.com/kotlin/ktx#livedata implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' // https://github.com/JakeWharton/timber - implementation 'com.jakewharton.timber:timber:4.7.1' + api 'com.jakewharton.timber:timber:4.7.1' // https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' // https://developer.android.com/reference/kotlin/androidx/preference/package-summary implementation 'androidx.preference:preference:1.1.1' // https://github.com/bumptech/glide - implementation 'com.github.bumptech.glide:glide:4.11.0' + api 'com.github.bumptech.glide:glide:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0' // https://github.com/afollestad/material-dialogs/ implementation 'com.afollestad.material-dialogs:core:3.3.0' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java index c19ce18..194d5c9 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java +++ b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java @@ -20,6 +20,8 @@ import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatImageView; /** @@ -27,6 +29,8 @@ * being displayed. */ public class RecyclingImageView extends AppCompatImageView { + @DrawableRes + private Integer mSavedDrawableRes = null; public RecyclingImageView(Context context) { super(context); @@ -36,11 +40,39 @@ public RecyclingImageView(Context context, AttributeSet attrs) { super(context, attrs); } + public void setSavedDrawableRes(@Nullable @DrawableRes Integer mSavedDrawableRes) { + this.mSavedDrawableRes = mSavedDrawableRes; + } + + @Nullable + @DrawableRes + public Integer getSavedDrawableRes() { + return mSavedDrawableRes; + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (mSavedDrawableRes != null && + visibility == VISIBLE && + getDrawable() == null) { + try { + GlideApp.with(this) + .load(mSavedDrawableRes) + .centerInside() + .into(this); + } catch (Exception ignored) { + setImageResource(mSavedDrawableRes); + } + } else if (visibility == INVISIBLE || visibility == GONE) + onDetachedFromWindow(); + } + /** * @see android.widget.ImageView#onDetachedFromWindow() */ @Override - protected void onDetachedFromWindow() { + public void onDetachedFromWindow() { // This has been detached from Window, so clear the drawable setImageDrawable(null); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index deb4cad..9029eef 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ tools:context=".activities.MainActivity"> + app:layout_constraintTop_toBottomOf="@+id/bar" /> - Intro de Handwashing reminder Política de privacidad Términos y condiciones Permite que la aplicación @@ -45,7 +44,6 @@ Permite que la aplicación monitorice el rendimiento de la misma y envíe datos anónimos para mejorar la experiencia de usuario - Anuncios Ad Construyendo tu aplicación… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8741206..b68ff46 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,7 +36,7 @@ try to wash your hands as soon as you can for avoiding diseases 💦👏 - Handwashing reminder intro + Handwashing reminder intro Privacy policy Terms and conditions Allow the application to @@ -45,7 +45,7 @@ Allow the application to track its performance and send anonymous data for improving in-app experience - Ads + Ads Ad Building your app… We are adding new diff --git a/appintro/build.gradle b/appintro/build.gradle index 5dbb6a6..d26d497 100644 --- a/appintro/build.gradle +++ b/appintro/build.gradle @@ -40,41 +40,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':app') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - // https://developers.google.com/android/guides/setup - implementation 'com.google.android.gms:play-services-location:17.0.0' - // https://material.io/develop/android/docs/getting-started/ - implementation 'com.google.android.material:material:1.1.0' - // https://github.com/Javinator9889/LocaleManager - implementation 'com.github.javinator9889:localemanager:1.1X' + // https://github.com/AppIntro/AppIntro implementation 'com.github.AppIntro:AppIntro:5.1.0' - // https://developer.android.com/jetpack/androidx/releases/cardview - implementation 'androidx.cardview:cardview:1.0.0' - // https://developer.android.com/jetpack/androidx/releases/recyclerview - implementation 'androidx.recyclerview:recyclerview:1.1.0' - // https://github.com/mikepenz/Android-Iconics - implementation 'com.mikepenz:iconics-core:5.0.2' - implementation 'com.mikepenz:iconics-views:5.0.2' - //noinspection GradleDependency - implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' - implementation 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar' - // https://developer.android.com/kotlin/ktx#play-core - implementation 'com.google.android.play:core-ktx:1.7.0' - // https://developer.android.com/studio/build/multidex - implementation 'androidx.multidex:multidex:2.0.1' - // http://airbnb.io/lottie/#/android?id=getting-started - implementation "com.airbnb.android:lottie:3.4.0" - // https://github.com/JakeWharton/timber - implementation 'com.jakewharton.timber:timber:4.7.1' - // https://firebase.google.com/docs/android/setup#add-sdks - implementation 'com.google.firebase:firebase-common-ktx:19.3.0' - implementation 'com.google.firebase:firebase-analytics:17.3.0' - implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' - implementation 'com.google.firebase:firebase-perf:19.0.6' - // https://github.com/bumptech/glide - implementation 'com.github.bumptech.glide:glide:4.11.0' + // https://github.com/mikepenz/FastAdapter + implementation "com.mikepenz:fastadapter:${latestFastAdapterRelease}" } diff --git a/appintro/src/main/AndroidManifest.xml b/appintro/src/main/AndroidManifest.xml index 78760b8..af282d9 100644 --- a/appintro/src/main/AndroidManifest.xml +++ b/appintro/src/main/AndroidManifest.xml @@ -14,12 +14,12 @@ - - + + + + + + diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt index d611cb4..39b6830 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -27,14 +27,9 @@ import android.os.Bundle import android.view.View import android.widget.FrameLayout import androidx.annotation.Keep -import androidx.core.app.ActivityCompat -import androidx.core.app.ActivityOptionsCompat import androidx.core.content.edit -import androidx.core.util.Pair -import androidx.core.util.forEach +import androidx.core.util.set import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.github.paolorotolo.appintro.AppIntro2 import com.github.paolorotolo.appintro.AppIntroViewPager import com.google.android.gms.common.ConnectionResult.SUCCESS @@ -45,25 +40,25 @@ import com.google.android.play.core.splitinstall.SplitInstallManagerFactory import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.perf.FirebasePerformance import com.javinator9889.handwashingreminder.activities.MainActivity -import com.javinator9889.handwashingreminder.appintro.config.TimeConfigActivity import com.javinator9889.handwashingreminder.appintro.custom.SliderPageBuilder import com.javinator9889.handwashingreminder.appintro.fragments.AnimatedAppIntro import com.javinator9889.handwashingreminder.appintro.fragments.SlidePolicyFragment import com.javinator9889.handwashingreminder.appintro.fragments.TimeConfigIntroFragment -import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigViewHolder +import com.javinator9889.handwashingreminder.appintro.fragments.TimeContainer +import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigItem import com.javinator9889.handwashingreminder.appintro.utils.AnimatedResources import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler -import com.javinator9889.handwashingreminder.listeners.ViewHolder import com.javinator9889.handwashingreminder.utils.* import kotlinx.android.synthetic.main.animated_intro.* import timber.log.Timber import com.javinator9889.handwashingreminder.appintro.R as RIntro +const val TIME_CONFIG_REQUEST_CODE = 16 + @Keep class IntroActivity : AppIntro2(), - ViewHolder.OnItemClickListener, AppIntroViewPager.OnNextPageRequestedListener, View.OnClickListener { private lateinit var activitySlide: Fragment @@ -100,10 +95,18 @@ class IntroActivity : AppIntro2(), .build() addSlide(secondSlide) - timeConfigSlide = TimeConfigIntroFragment() - timeConfigSlide.bgColor = Color.WHITE - timeConfigSlide.listener = this - timeConfigSlide.fromActivity = this + var timeFragment: TimeConfigIntroFragment? = null + if (savedInstanceState != null) { + timeFragment = + supportFragmentManager.getFragment( + savedInstanceState, + TimeConfigIntroFragment::class.simpleName!! + ) as TimeConfigIntroFragment? + } + if (timeFragment == null) { + timeConfigSlide = TimeConfigIntroFragment() + timeConfigSlide.bgColor = Color.WHITE + } else timeConfigSlide = timeFragment addSlide(timeConfigSlide) val gms = GoogleApiAvailability.getInstance() @@ -117,13 +120,22 @@ class IntroActivity : AppIntro2(), addSlide(activitySlide) } - policySlide = SlidePolicyFragment().apply { - title = this@IntroActivity - .getString(RIntro.string.privacy_policy_title) - animatedDrawable = AnimatedResources.PRIVACY - titleColor = Color.DKGRAY - bgColor = Color.WHITE + var policyConfig: SlidePolicyFragment? = null + if (savedInstanceState != null) { + policyConfig = supportFragmentManager.getFragment( + savedInstanceState, + SlidePolicyFragment::class.simpleName!! + ) as SlidePolicyFragment? } + if (policyConfig == null) { + policySlide = SlidePolicyFragment().apply { + title = this@IntroActivity + .getString(RIntro.string.privacy_policy_title) + animatedDrawable = AnimatedResources.PRIVACY + titleColor = Color.DKGRAY + bgColor = Color.WHITE + } + } else policySlide = policyConfig addSlide(policySlide) showSkipButton(false) @@ -133,12 +145,30 @@ class IntroActivity : AppIntro2(), nextButton.setOnClickListener(this) } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + runCatching { + supportFragmentManager.putFragment( + outState, + TimeConfigIntroFragment::class.simpleName!!, + timeConfigSlide + ) + } + runCatching { + supportFragmentManager.putFragment( + outState, + SlidePolicyFragment::class.simpleName!!, + policySlide + ) + } + } + override fun onDonePressed(currentFragment: Fragment?) { super.onDonePressed(currentFragment) val app = HandwashingApplication.getInstance() val sharedPreferences = app.sharedPreferences sharedPreferences.edit(commit = true) { - timeConfigSlide.rvItems.forEach { item -> + timeConfigSlide.itemAdapter.adapterItems.forEach { item -> val time = "${item.hours}:${item.minutes}" when (item.id) { TimeConfig.BREAKFAST_ID -> @@ -216,78 +246,38 @@ class IntroActivity : AppIntro2(), this.finish() } - override fun onItemClick( - viewHolder: RecyclerView.ViewHolder?, - view: View?, - position: Int, - id: Long - ) { - if (viewHolder == null || viewHolder !is TimeConfigViewHolder) - return - val intent = Intent(this, TimeConfigActivity::class.java) - val options = if (isAtLeast(AndroidVersion.LOLLIPOP)) { - val pairs = mutableListOf>() - val items = HashMap(6).apply { - this[TimeConfigActivity.VIEW_TITLE_NAME] = viewHolder.title - this[TimeConfigActivity.INFO_IMAGE_NAME] = viewHolder.image - this[TimeConfigActivity.USER_TIME_ICON] = viewHolder.clockIcon - this[TimeConfigActivity.USER_TIME_HOURS] = viewHolder.hours - this[TimeConfigActivity.USER_DDOT] = viewHolder.ddot - this[TimeConfigActivity.USER_TIME_MINUTES] = viewHolder.minutes - } - val lm = - timeConfigSlide.recyclerView.layoutManager as LinearLayoutManager - if (position <= lm.findLastCompletelyVisibleItemPosition()) { - items.onEach { - pairs.add(Pair.create(it.value, it.key)) - } - } - ActivityOptionsCompat.makeSceneTransitionAnimation( - this, - *pairs.toTypedArray() - ) - } else { - null - } - intent.putExtra( - "title", viewHolder.title.text - ) - intent.putExtra( - "hours", viewHolder.hours.text - ) - intent.putExtra( - "minutes", viewHolder.minutes.text - ) - intent.putExtra("id", id) - ActivityCompat.startActivityForResult( - this, intent, id.toInt(), options?.toBundle() - ) - } - override fun onActivityResult( requestCode: Int, resultCode: Int, data: Intent? ) { super.onActivityResult(requestCode, resultCode, data) - if (data == null) + if (data == null || requestCode != TIME_CONFIG_REQUEST_CODE) return - val view: TimeConfigViewHolder = when (requestCode.toLong()) { - TimeConfig.BREAKFAST_ID -> { - timeConfigSlide.viewItems[TimeConfig.BREAKFAST_ID.toInt()] - } - TimeConfig.LUNCH_ID -> { - timeConfigSlide.viewItems[TimeConfig.LUNCH_ID.toInt()] + val id = data.getLongExtra("id", 0L) + if (timeConfigSlide.isInitialized) { + val position = data.getIntExtra("position", 0) + val hours = data.getStringExtra("hours") + val minutes = data.getStringExtra("minutes") + val titleText = when(id) { + TimeConfig.BREAKFAST_ID -> getString(RIntro.string.breakfast) + TimeConfig.LUNCH_ID -> getString(RIntro.string.lunch) + TimeConfig.DINNER_ID -> getString(RIntro.string.dinner) + else -> "" } - TimeConfig.DINNER_ID -> { - timeConfigSlide.viewItems[TimeConfig.DINNER_ID.toInt()] - } - else -> null - } as TimeConfigViewHolder - view.hours.text = data.getStringExtra("hours") - view.minutes.text = data.getStringExtra("minutes") - view.saveContentToTextViews() - setSwipeLock() + timeConfigSlide.itemAdapter[position] = TimeConfigItem( + getString(RIntro.string.time_config_title_tpl, titleText), + id, hours, minutes + ) + timeConfigSlide.fastAdapter.notifyAdapterItemChanged(position) + setSwipeLock() + } else { + timeConfigSlide.propertyContainer[id.toInt()] = + TimeContainer( + data.getStringExtra("hours"), + data.getStringExtra("minutes") + ) + } } override fun onSlideChanged( @@ -301,13 +291,15 @@ class IntroActivity : AppIntro2(), setSwipeLock(false) } if (oldFragment == activitySlide) - askForPermissions( - this, - Permission( - Manifest.permission.ACTIVITY_RECOGNITION, - PERMISSIONS_REQUEST_CODE + if (isAtLeast(AndroidVersion.Q)) { + askForPermissions( + this, + Permission( + Manifest.permission.ACTIVITY_RECOGNITION, + PERMISSIONS_REQUEST_CODE + ) ) - ) + } if (newFragment is AnimatedAppIntro || newFragment is SlidePolicyFragment ) @@ -317,8 +309,8 @@ class IntroActivity : AppIntro2(), private fun setSwipeLock() { var swipeLock = false - timeConfigSlide.viewItems.forEach { _, value -> - if (value.hours.text == "" && value.minutes.text == "") { + timeConfigSlide.itemAdapter.adapterItems.forEach { item -> + if (item.hours.isNullOrEmpty() && item.minutes.isNullOrEmpty()) { swipeLock = true return@forEach } @@ -332,9 +324,7 @@ class IntroActivity : AppIntro2(), return when (timeConfigIntroFragment) { timeConfigSlide -> { var isTimeSet = true - if (timeConfigSlide.rvItems.size != 3) - return false - timeConfigSlide.rvItems.forEach { item -> + timeConfigSlide.itemAdapter.adapterItems.forEach { item -> val hours = item.hours val minutes = item.minutes if (hours == "" || minutes == "") { diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/config/TimeConfigActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/config/TimeConfigActivity.kt index 2244612..13ca97d 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/config/TimeConfigActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/config/TimeConfigActivity.kt @@ -40,6 +40,7 @@ import com.javinator9889.handwashingreminder.utils.formatTime import com.javinator9889.handwashingreminder.utils.isAtLeast import timber.log.Timber import java.util.* +import kotlin.properties.Delegates class TimeConfigActivity : ActionBarBase(), @@ -64,6 +65,8 @@ class TimeConfigActivity : private lateinit var hours: TextView private lateinit var minutes: TextView private lateinit var clockIcon: ImageView + private var id by Delegates.notNull() + private var position by Delegates.notNull() data class Time(val hour: Int, val minute: Int) @@ -104,7 +107,9 @@ class TimeConfigActivity : val sHours = data!!.getCharSequence("hours") val sMinutes = data.getCharSequence("minutes") title.text = data.getCharSequence("title") - val imageRes = when (data.getLong("id")) { + id = data.getLong("id", TimeConfig.BREAKFAST_ID) + position = data.getInt("position", 0) + val imageRes = when (id) { TimeConfig.BREAKFAST_ID -> R.drawable.ic_breakfast TimeConfig.LUNCH_ID -> R.drawable.ic_lunch TimeConfig.DINNER_ID -> R.drawable.ic_dinner @@ -153,6 +158,8 @@ class TimeConfigActivity : outState.putCharSequence("hours", hour) outState.putCharSequence("minutes", minute) outState.putCharSequence("title", title.text) + outState.putLong("id", id) + outState.putInt("position", position) } override fun onBackPressed() { @@ -160,6 +167,8 @@ class TimeConfigActivity : val tpTime = getHours() intent.putExtra("hours", formatTime(tpTime.hour)) intent.putExtra("minutes", formatTime(tpTime.minute)) + intent.putExtra("id", id) + intent.putExtra("position", position) setResult(Activity.RESULT_OK, intent) if (isAtLeast(AndroidVersion.LOLLIPOP)) finishAfterTransition() diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/fragments/TimeConfigIntroFragment.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/fragments/TimeConfigIntroFragment.kt index bc3bd14..54d4bc9 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/fragments/TimeConfigIntroFragment.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/fragments/TimeConfigIntroFragment.kt @@ -18,107 +18,147 @@ */ package com.javinator9889.handwashingreminder.appintro.fragments -import android.content.Context +import android.content.Intent import android.graphics.Color import android.os.Bundle import android.util.SparseArray import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.core.util.forEach +import androidx.core.app.ActivityCompat +import androidx.core.app.ActivityOptionsCompat +import androidx.core.util.Pair +import androidx.core.util.set +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.paolorotolo.appintro.AppIntroBaseFragment -import com.google.android.play.core.splitcompat.SplitCompat import com.javinator9889.handwashingreminder.appintro.R -import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigAdapter -import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigContent -import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigViewHolder -import com.javinator9889.handwashingreminder.listeners.ViewHolder +import com.javinator9889.handwashingreminder.appintro.TIME_CONFIG_REQUEST_CODE +import com.javinator9889.handwashingreminder.appintro.config.TimeConfigActivity +import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigItem +import com.javinator9889.handwashingreminder.utils.AndroidVersion import com.javinator9889.handwashingreminder.utils.TimeConfig -import com.javinator9889.handwashingreminder.utils.notNull +import com.javinator9889.handwashingreminder.utils.isAtLeast +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.listeners.ClickEventHook +import kotlinx.android.synthetic.main.time_card_view.view.* import kotlinx.android.synthetic.main.time_config.view.* -import com.javinator9889.handwashingreminder.R as RBase class TimeConfigIntroFragment : AppIntroBaseFragment() { - private lateinit var rvAdapter: TimeConfigAdapter - lateinit var rvItems: Array - lateinit var recyclerView: RecyclerView - lateinit var fromActivity: AppCompatActivity var bgColor: Int = Color.WHITE - var listener: ViewHolder.OnItemClickListener? = null - val viewItems = SparseArray(3) - + var isInitialized = false + val propertyContainer = SparseArray(3) + lateinit var recyclerView: RecyclerView + lateinit var fastAdapter: FastAdapter + lateinit var itemAdapter: ItemAdapter - override fun onAttach(context: Context) { - super.onAttach(context) - SplitCompat.installActivity(activity) + init { + propertyContainer[TimeConfig.BREAKFAST_ID.toInt()] = TimeContainer() + propertyContainer[TimeConfig.LUNCH_ID.toInt()] = TimeContainer() + propertyContainer[TimeConfig.DINNER_ID.toInt()] = TimeContainer() } + @Suppress("unchecked_cast") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val breakfast = getString(R.string.breakfast) val lunch = getString(R.string.lunch) val dinner = getString(R.string.dinner) - rvItems = arrayOf( - TimeConfigContent( - getString(R.string.time_config_title_tpl, breakfast), - TimeConfig.BREAKFAST_ID - ), - TimeConfigContent( - getString(R.string.time_config_title_tpl, lunch), - TimeConfig.LUNCH_ID - ), - TimeConfigContent( - getString(R.string.time_config_title_tpl, dinner), - TimeConfig.DINNER_ID + itemAdapter = ItemAdapter().apply { + val items = listOf( + TimeConfigItem( + getString(R.string.time_config_title_tpl, breakfast), + TimeConfig.BREAKFAST_ID, + propertyContainer[TimeConfig.BREAKFAST_ID.toInt()].hours, + propertyContainer[TimeConfig.BREAKFAST_ID.toInt()].minutes + ), + TimeConfigItem( + getString(R.string.time_config_title_tpl, lunch), + TimeConfig.LUNCH_ID, + propertyContainer[TimeConfig.LUNCH_ID.toInt()].hours, + propertyContainer[TimeConfig.LUNCH_ID.toInt()].minutes + ), + TimeConfigItem( + getString(R.string.time_config_title_tpl, dinner), + TimeConfig.DINNER_ID, + propertyContainer[TimeConfig.DINNER_ID.toInt()].hours, + propertyContainer[TimeConfig.DINNER_ID.toInt()].minutes + ) ) - ) - rvAdapter = - TimeConfigAdapter( - rvItems, - listener, - viewItems - ) - } - - @Suppress("unchecked_cast") - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - savedInstanceState.notNull { - val restoredItems = it.getParcelableArray("rvItems") - as Array - restoredItems.forEachIndexed { i, timeConfigContent -> - rvItems[i].hours = timeConfigContent.hours - rvItems[i].minutes = timeConfigContent.minutes - } - viewItems.forEach { _, value -> value.loadContentToTextViews() } + setNewList(items) } } - override fun onSaveInstanceState(outState: Bundle) { - outState.putParcelableArray("rvItems", rvItems) - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(layoutId, container, false) - val rvManager = LinearLayoutManager(context) - fromActivity.setSupportActionBar(view.findViewById(RBase.id.toolbar)) - recyclerView = - view.cardsView.apply { - setHasFixedSize(true) - layoutManager = rvManager - adapter = rvAdapter - } - recyclerView.setBackgroundColor(bgColor) + ): View = inflater.inflate(layoutId, container, false) - return view + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val manager = LinearLayoutManager(context) + fastAdapter = FastAdapter.with(itemAdapter) + recyclerView = view.cardsView.apply { + setHasFixedSize(true) + layoutManager = manager + adapter = fastAdapter + setBackgroundColor(bgColor) + } + isInitialized = true + fastAdapter.addEventHook(object : ClickEventHook() { + override fun onBind(viewHolder: RecyclerView.ViewHolder) = + if (viewHolder is TimeConfigItem.ViewHolder) viewHolder.cardView + else null + + override fun onClick( + v: View, + position: Int, + fastAdapter: FastAdapter, + item: TimeConfigItem + ) { + val intent = Intent(context, TimeConfigActivity::class.java) + val options = if (isAtLeast(AndroidVersion.LOLLIPOP)) { + val pairs = mutableListOf>() + val items = HashMap(6).apply { + this[TimeConfigActivity.VIEW_TITLE_NAME] = v.title + this[TimeConfigActivity.INFO_IMAGE_NAME] = v.infoImage + this[TimeConfigActivity.USER_TIME_ICON] = v.clockIcon + this[TimeConfigActivity.USER_TIME_HOURS] = v.hours + this[TimeConfigActivity.USER_DDOT] = v.ddot + this[TimeConfigActivity.USER_TIME_MINUTES] = v.minutes + } + items.onEach { + if (it.value.isVisible) + pairs.add(Pair.create(it.value, it.key)) + } + ActivityOptionsCompat.makeSceneTransitionAnimation( + requireActivity(), + *pairs.toTypedArray() + ) + } else { + null + } + intent.apply { + putExtra("title", v.title.text) + putExtra("hours", v.hours.text) + putExtra("minutes", v.minutes.text) + putExtra("id", item.id) + putExtra("position", position) + } + ActivityCompat.startActivityForResult( + requireActivity(), + intent, + TIME_CONFIG_REQUEST_CODE, + options?.toBundle() + ) + } + }) } override fun getLayoutId(): Int = R.layout.time_config -} \ No newline at end of file +} + +data class TimeContainer(val hours: String? = "", val minutes: String? = "") \ No newline at end of file diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigAdapter.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigAdapter.kt index 372616c..9b36ad2 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigAdapter.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigAdapter.kt @@ -31,8 +31,7 @@ class TimeConfigAdapter( private val dataset: Array, private val listener: ViewHolder.OnItemClickListener?, private val viewItems: SparseArray -) : - RecyclerView.Adapter() { +) : RecyclerView.Adapter() { private var height = 0 private lateinit var context: Context diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt new file mode 100644 index 0000000..1720ca2 --- /dev/null +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt @@ -0,0 +1,90 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 26/04/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.appintro.timeconfig + +import android.view.View +import android.widget.TextView +import androidx.annotation.LayoutRes +import androidx.cardview.widget.CardView +import com.javinator9889.handwashingreminder.appintro.R +import com.javinator9889.handwashingreminder.graphics.GlideApp +import com.javinator9889.handwashingreminder.graphics.RecyclingImageView +import com.javinator9889.handwashingreminder.utils.TimeConfig +import com.javinator9889.handwashingreminder.utils.notNull +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import com.mikepenz.iconics.utils.sizeDp +import com.mikepenz.iconics.view.IconicsImageView + +class TimeConfigItem( + val title: CharSequence, + val id: Long, + var hours: String? = "", + var minutes: String? = "" +) : AbstractItem() { + @LayoutRes + override val layoutRes: Int = R.layout.time_card_view + override val type: Int = R.id.timeCard + + override fun getViewHolder(v: View) = ViewHolder(v) + + class ViewHolder(private val view: View) : + FastAdapter.ViewHolder(view) { + private val title: TextView = view.findViewById(R.id.title) + private val hours: TextView = view.findViewById(R.id.hours) + private val ddot: TextView = view.findViewById(R.id.ddot) + private val minutes: TextView = view.findViewById(R.id.minutes) + private val image: RecyclingImageView = view.findViewById(R.id.infoImage) + private val clockIcon: IconicsImageView = view.findViewById(R.id.clockIcon) + val cardView: CardView = view.findViewById(R.id.timeCard) + + override fun bindView(item: TimeConfigItem, payloads: List) { + title.text = item.title + hours.text = item.hours + minutes.text = item.minutes + ddot.text = view.context.getString(R.string.double_dot) + clockIcon.icon = + IconicsDrawable(view.context, Ionicons.Icon.ion_android_time) + .apply { sizeDp = 16 } + when (item.id) { + TimeConfig.BREAKFAST_ID -> R.drawable.ic_breakfast + TimeConfig.LUNCH_ID -> R.drawable.ic_lunch + TimeConfig.DINNER_ID -> R.drawable.ic_dinner + else -> null + }.notNull { + GlideApp.with(view) + .load(it) + .centerInside() + .into(image) + image.savedDrawableRes = it + } + } + + override fun unbindView(item: TimeConfigItem) { + title.text = null + hours.text = null + ddot.text = null + minutes.text = null + image.onDetachedFromWindow() + clockIcon.icon = null + } + } +} \ No newline at end of file diff --git a/appintro/src/main/res/layout-land/time_card_view_expanded.xml b/appintro/src/main/res/layout-land/time_card_view_expanded.xml new file mode 100644 index 0000000..e91e868 --- /dev/null +++ b/appintro/src/main/res/layout-land/time_card_view_expanded.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +