diff --git a/.gitignore b/.gitignore index 2c9df55..9752de8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ secrets.properties api-5245190277294621651-718463-f914fb7573c6.json *.jks google-services.json +services.xml diff --git a/CHANGELOG b/CHANGELOG index 2b660be..84f6a14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +## v1.1.1 ++ New notifications handler - now you must get notified when expected + ## v1.1.0 + NEW: OkHttp for handling file downloading. + Corrected a minor bug while displaying images. diff --git a/README.md b/README.md index 6683287..712aa89 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ will try to implement classic APK version. [Download from Play Store](https://play.google.com/store/apps/details?id=com.javinator9889.handwashingreminder) + +Get it on Google Play + ## Contributing If you want to contribute: diff --git a/ads/build.gradle b/ads/build.gradle index 0719842..12eb5ec 100644 --- a/ads/build.gradle +++ b/ads/build.gradle @@ -33,7 +33,7 @@ dependencies { // 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.0.1' + 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 diff --git a/app/build.gradle b/app/build.gradle index 121cf7f..25c689b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,9 +3,8 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' apply plugin: 'com.mikepenz.aboutlibraries.plugin' -apply plugin: 'com.google.gms.google-services' -apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.firebase-perf' +apply plugin: 'com.google.firebase.crashlytics' def secretsPropertiesFile = rootProject.file("secrets.properties") @@ -43,8 +42,8 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 103 - versionName "1.1.0-${gitCommitHash}" + versionCode 107 + versionName "1.1.1-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -143,7 +142,6 @@ dependencies { // 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-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' @@ -152,6 +150,7 @@ dependencies { implementation "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://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 @@ -172,7 +171,7 @@ dependencies { // 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.0' + implementation 'androidx.preference:preference:1.1.1' // https://github.com/bumptech/glide implementation 'com.github.bumptech.glide:glide:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 531af97..4d1d5e2 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -14,6 +14,7 @@ # Uncomment this to preserve the line number information for # debugging stack traces. +-keepattributes *Annotation* -keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to @@ -78,3 +79,7 @@ #data models -keep class com.javinator9889.handwashingreminder.collections.** { *;} + +# prevent Crashlytics obfuscation +-keep class com.google.firebase.crashlytics.** { *; } +-dontwarn com.google.firebase.crashlytics.** diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5d6b744..a4984aa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + @@ -75,6 +76,10 @@ + \ No newline at end of file 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 d31fdd6..203aa7f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt @@ -116,9 +116,8 @@ class DynamicFeatureProgress : SplitCompatBaseActivity(), SplitInstallSessionStatus.FAILED -> { Toast.makeText( this, getString( - R.string - .dynamic_module_loading_error - ), Toast.LENGTH_LONG + R.string.dynamic_module_loading_error, state.errorCode), + Toast.LENGTH_LONG ).show() Timber.e( "Installation failed - error code: ${state.errorCode}" 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 7db66e8..f229aa9 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -32,15 +32,17 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.android.play.core.splitcompat.SplitCompat import com.google.android.play.core.splitinstall.SplitInstallManagerFactory import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.ktx.Firebase import com.google.firebase.perf.FirebasePerformance -import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings +import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication 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.utils.* import com.javinator9889.handwashingreminder.utils.Preferences.Companion.ADS_ENABLED import com.javinator9889.handwashingreminder.utils.Preferences.Companion.APP_INIT_KEY @@ -54,6 +56,7 @@ import timber.log.Timber import java.security.Security import java.util.* import kotlin.collections.ArrayList +import com.javinator9889.handwashingreminder.utils.Firebase as FirebaseConf internal const val FAST_START_KEY = "intent:fast_start" internal const val PENDING_INTENT_CODE = 201 @@ -94,7 +97,8 @@ class LauncherActivity : AppCompatActivity() { } private suspend fun displayWelcomeScreen() { - val isThereAnySpecialEvent = with(FirebaseRemoteConfig.getInstance()) { + app.firebaseInitDeferred.await() + val isThereAnySpecialEvent = with(Firebase.remoteConfig) { getBoolean(SPECIAL_EVENT) && !launchFromNotification } var sleepDuration = 0L @@ -185,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 @@ -235,7 +239,9 @@ class LauncherActivity : AppCompatActivity() { it.putExtra(DynamicFeatureProgress.PACKAGE_NAME, packageName) } - private fun initVariables() { + private suspend fun initVariables() { + app.firebaseInitDeferred.await() + Timber.d("Firebase initialized correctly") Timber.d("Initializing Iconics") Iconics.init(this) Timber.d("Setting-up security providers") @@ -254,19 +260,17 @@ class LauncherActivity : AppCompatActivity() { } Timber.d("Initializing Billing Service") app.billingService = BillingService(this) - try { - app.workHandler.enqueuePeriodicNotificationsWorker() - Timber.d("Adding periodic notifications if not enqueued yet") - } catch (_: UninitializedPropertyAccessException) { - Timber.i("Scheduler times have not been initialized") + with(WorkHandler(this)) { + enqueuePeriodicNotificationsWorker() } + Timber.d("Adding periodic notifications if not enqueued yet") Timber.d("Setting-up Firebase custom properties") setupFirebaseProperties() } private fun setupFirebaseProperties() { val firebaseAnalytics = FirebaseAnalytics.getInstance(this) - val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance() + val firebaseRemoteConfig = Firebase.remoteConfig val firebasePerformance = FirebasePerformance.getInstance() val config = with(FirebaseRemoteConfigSettings.Builder()) { minimumFetchIntervalInSeconds = 10 @@ -280,14 +284,14 @@ class LauncherActivity : AppCompatActivity() { when (Locale.getDefault().language) { Locale(LanguagesSupport.Language.SPANISH).language -> { firebaseAnalytics.setUserProperty( - Firebase.Properties.LANGUAGE, + FirebaseConf.Properties.LANGUAGE, LanguagesSupport.Language.SPANISH ) R.xml.remote_config_defaults_es } else -> { firebaseAnalytics.setUserProperty( - Firebase.Properties.LANGUAGE, + FirebaseConf.Properties.LANGUAGE, LanguagesSupport.Language.ENGLISH ) R.xml.remote_config_defaults diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 74fb405..e6be2f5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -29,8 +29,9 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.ktx.Firebase import com.google.firebase.perf.metrics.AddTrace -import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment @@ -61,7 +62,7 @@ class MainActivity : ActionBarBase(), with(FirebaseAnalytics.getInstance(this)) { setCurrentScreen(this@MainActivity, "Main view", null) } - with(FirebaseRemoteConfig.getInstance()) { + with(Firebase.remoteConfig) { fetchAndActivate() } delegateMenuIcons(menu) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 667eacb..6cad6a3 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -21,7 +21,8 @@ package com.javinator9889.handwashingreminder.activities.views.fragments.news import android.os.Bundle import android.view.View import androidx.annotation.LayoutRes -import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.utils.RemoteConfig @@ -40,7 +41,7 @@ class NewsFragment : BaseFragmentView() { ARG_UNDER_CONSTRUCTION_TEXT ) } else { - with(FirebaseRemoteConfig.getInstance()) { + with(Firebase.remoteConfig) { underConstructionText.text = getString(RemoteConfig.WORK_IN_PROGRESS) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt index 5575ce4..aa3923e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt @@ -45,6 +45,8 @@ class ActivityCheckbox : CheckBoxPreference { defStyleRes: Int ) : super(context, attrs, defStyleAttr, defStyleRes) + private var firstCheck = true + init { var isViewDisabled = false with(GoogleApiAvailability.getInstance()) { @@ -83,6 +85,10 @@ class ActivityCheckbox : CheckBoxPreference { override fun setChecked(checked: Boolean) { super.setChecked(checked) + if (firstCheck) { + firstCheck = false + return + } with(HandwashingApplication.getInstance()) { if (checked) { activityHandler.startTrackingActivity() 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 a182432..b9fb6a6 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 @@ -29,6 +29,7 @@ import com.mikepenz.iconics.utils.sizeDp import java.util.* class ActivityMultiSelectList : MultiSelectListPreference { + private var isFirstCall = true constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : @@ -48,7 +49,10 @@ class ActivityMultiSelectList : MultiSelectListPreference { override fun notifyChanged() { super.notifyChanged() loadSummary() - reloadActivityHandler() + if (!isFirstCall) + reloadActivityHandler() + else + isFirstCall = false } private fun loadSummary() { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt index f582f0b..e978ada 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt @@ -27,7 +27,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import com.beust.klaxon.Klaxon -import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.collections.DiseasesInformation import com.javinator9889.handwashingreminder.collections.DiseasesList import com.javinator9889.handwashingreminder.collections.DiseasesListWrapper @@ -55,7 +56,7 @@ class DiseaseInformationViewModel( state.get>(PARSED_JSON_KEY)!! ) val diseasesString = - with(FirebaseRemoteConfig.getInstance()) { + with(Firebase.remoteConfig) { getString(DISEASES_JSON) } Klaxon().parse(diseasesString) 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 2c03d8f..dbd3a77 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 @@ -42,7 +42,6 @@ import java.io.FileInputStream import java.io.InputStream import java.math.BigInteger import java.security.MessageDigest -import javax.inject.Inject private const val LIVEDATA_KEY = "videomodel:livedata" @@ -128,7 +127,7 @@ class VideoModel( } } -class VideoModelFactory @Inject constructor(private val position: Int) : +class VideoModelFactory constructor(private val position: Int) : ViewModelAssistedFactory { override fun create(handle: SavedStateHandle) = VideoModel(handle, position) } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt index b3bf94a..afa4234 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt @@ -27,7 +27,6 @@ import androidx.lifecycle.liveData import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.emoji.EmojiLoader -import javax.inject.Inject internal data class Measurements(var width: Int, var height: Int) @@ -102,7 +101,7 @@ class WashingHandsModel( } } -class WashingHandsModelFactory @Inject constructor( +class WashingHandsModelFactory constructor( private val position: Int ) : ViewModelAssistedFactory { override fun create(handle: SavedStateHandle) = 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 ca67d1e..4929eaf 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -20,27 +20,30 @@ 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 +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions import com.google.firebase.crashlytics.FirebaseCrashlytics import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.gms.vendor.BillingService -import com.javinator9889.handwashingreminder.jobs.workers.WorkHandler import com.javinator9889.handwashingreminder.utils.LogReportTree import com.javinator9889.handwashingreminder.utils.isDebuggable import javinator9889.localemanager.application.BaseApplication import javinator9889.localemanager.utils.languagesupport.LanguagesSupport.Language +import kotlinx.coroutines.* import timber.log.Timber class HandwashingApplication : BaseApplication() { var adLoader: AdLoader? = null - lateinit var workHandler: WorkHandler lateinit var billingService: BillingService lateinit var activityHandler: ActivityHandler lateinit var sharedPreferences: SharedPreferences + lateinit var firebaseInitDeferred: Deferred companion object { private lateinit var instance: HandwashingApplication @@ -63,8 +66,8 @@ class HandwashingApplication : BaseApplication() { super.onCreate() instance = this sharedPreferences = getCustomSharedPreferences(this) - - if (isDebuggable()) { + activityHandler = ActivityHandler(this) + /*if (isDebuggable()) { Timber.plant(Timber.DebugTree()) Timber.d("Application is in DEBUG mode") with(FirebaseCrashlytics.getInstance()) { @@ -72,9 +75,30 @@ class HandwashingApplication : BaseApplication() { } } else { Timber.plant(LogReportTree()) + }*/ + firebaseInitDeferred = initFirebaseAppAsync() + Log.d("Application", "Deferred Firebase Instantiating") + } + + private fun initFirebaseAppAsync(): Deferred { + return GlobalScope.async { + withContext(Dispatchers.IO) { + FirebaseApp.initializeApp( + this@HandwashingApplication, + FirebaseOptions + .fromResource(this@HandwashingApplication)!! + ) + if (isDebuggable()) { + Timber.plant(Timber.DebugTree()) + Timber.d("Application is in DEBUG mode") + with(FirebaseCrashlytics.getInstance()) { + setCrashlyticsCollectionEnabled(false) + } + } else { + Timber.plant(LogReportTree()) + } + } } - activityHandler = ActivityHandler(this) - workHandler = WorkHandler(this) } /** diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt index 98386ca..1f6019a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt @@ -38,10 +38,12 @@ object EmojiLoader { emojiCompat.registerInitCallback( object : EmojiCompat.InitCallback() { override fun onInitialized() { + emojiCompat.unregisterInitCallback(this) deferred.complete(EmojiCompat.get()) } override fun onFailed(throwable: Throwable?) { + emojiCompat.unregisterInitCallback(this) val exception = throwable ?: RuntimeException("EmojiCompat failed to load") deferred.completeExceptionally(exception) 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 1af5a8c..69d0cdd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt @@ -22,6 +22,7 @@ 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.utils.Preferences import timber.log.Timber @@ -39,9 +40,14 @@ class BootCompletedJob : BroadcastReceiver() { else app.activityHandler.disableActivityTracker() try { - app.workHandler.enqueuePeriodicNotificationsWorker() - } catch (e: UninitializedPropertyAccessException) { - Timber.e(e, "Schedule times have not been initialized yet") + 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" + ) } } } 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 9a0b4c7..df52419 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/UpdateReceiver.kt @@ -21,12 +21,22 @@ package com.javinator9889.handwashingreminder.jobs 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 timber.log.Timber class UpdateReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - with(HandwashingApplication.getInstance().workHandler) { - enqueuePeriodicNotificationsWorker(true) + 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" + ) + } } } } \ No newline at end of file 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 new file mode 100644 index 0000000..29f7136 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/AbstractNotificationsWorker.kt @@ -0,0 +1,170 @@ +/* + * 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 new file mode 100644 index 0000000..252fad2 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/BreakfastWorker.kt @@ -0,0 +1,36 @@ +/* + * 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 new file mode 100644 index 0000000..8465623 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/DinnerWorker.kt @@ -0,0 +1,35 @@ +/* + * 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 new file mode 100644 index 0000000..404c102 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/LunchWorker.kt @@ -0,0 +1,35 @@ +/* + * 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/NotificationsWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/NotificationsWorker.kt deleted file mode 100644 index 87e9616..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/NotificationsWorker.kt +++ /dev/null @@ -1,153 +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 10/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.jobs.workers - -import android.content.Context -import androidx.annotation.ArrayRes -import androidx.annotation.StringRes -import androidx.work.CoroutineWorker -import androidx.work.Data -import androidx.work.WorkerParameters -import com.javinator9889.handwashingreminder.R -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.Workers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.withContext -import timber.log.Timber -import java.util.* - - -data class NotificationStructure( - @StringRes val title: Int, - @ArrayRes val content: Int -) - -class NotificationsWorker( - private val context: Context, - params: WorkerParameters -) : CoroutineWorker(context, params) { - - override suspend fun doWork(): Result = coroutineScope { - try { - val emojiLoader = EmojiLoader.get(context) - val notificationsHandler = NotificationsHandler( - context, - TIME_CHANNEL_ID, - context.getString(R.string.time_notification_channel_name), - context.getString(R.string.time_notification_channel_desc) - ) - val workHandler = WorkHandler(context) - - val notificationData = - setNotificationData( - inputData.getInt( - Workers.WHO, - -1 - ) - ) - val delay = nextExecutionDelay(inputData) - if (delay == -1L) - return@coroutineScope Result.failure() - - - val emojiCompat = emojiLoader.await() - var title: CharSequence - var comment: CharSequence - try { - title = - emojiCompat.process(context.getString(notificationData.title)) - val comments = - context.resources.getStringArray(notificationData.content) - comment = emojiCompat.process(comments.asList().random()) - } catch (_: IllegalStateException) { - title = context.getText(notificationData.title) - comment = context.resources - .getStringArray(notificationData.content).asList().random() - } - - withContext(Dispatchers.Main) { - notificationsHandler.createNotification( - R.drawable.ic_handwashing_icon, - R.drawable.handwashing_app_logo, - title, - comment, - longContent = comment - ) - } - - with(Data.Builder()) { - putAll(inputData) - build() - }.let { - Timber.d(it.toString()) - workHandler.enqueueNotificationsWorker(delay, it) - } - Result.success() - } catch (e: Exception) { - Timber.e(e, "Uncaught exception on worker class") - if (runAttemptCount < 5) - Result.retry() - else - Result.failure() - } - } - - private fun setNotificationData(who: Int): NotificationStructure = - when (who) { - Workers.BREAKFAST -> - NotificationStructure( - R.string.breakfast_title, - R.array.breakfast_comments - ) - Workers.LUNCH -> - NotificationStructure( - R.string.lunch_title, - R.array.lunch_comments - ) - Workers.DINNER -> - NotificationStructure( - R.string.dinner_title, - R.array.dinner_comments - ) - else -> throw IllegalArgumentException("Worker $who not found") - } - - private fun nextExecutionDelay(data: Data): Long { - val currentDate = Calendar.getInstance() - val dueDate = Calendar.getInstance() - - val hour = data.getInt(Workers.HOUR, -1) - val minute = data.getInt(Workers.MINUTE, -1) - if (hour == -1 || hour == -1) { - Timber.e("Hour or minute not provided") - return -1L - } - - dueDate.set(Calendar.HOUR_OF_DAY, hour) - dueDate.set(Calendar.MINUTE, minute) - dueDate.set(Calendar.SECOND, 0) - if (dueDate.before(currentDate)) - dueDate.add(Calendar.HOUR_OF_DAY, 24) - Timber.i("Next execution scheduled at: ${dueDate.time}") - return dueDate.timeInMillis - currentDate.timeInMillis - } -} 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 index 90898da..b04539d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/WorkHandler.kt @@ -23,18 +23,21 @@ 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.* import java.util.concurrent.TimeUnit -data class Who(val uuid: String, val id: Int) +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 currentDate = Calendar.getInstance() val preferences = PreferenceManager.getDefaultSharedPreferences(context) val breakfastTime = @@ -47,34 +50,37 @@ class WorkHandler(private val context: Context) { } val times = arrayOf(breakfastTime, lunchTime, dinnerTime) times.forEach { time -> - val dueDate = Calendar.getInstance() val splittedTime = time.split(":") val hour = Integer.parseInt(splittedTime[0].trim()) val minute = Integer.parseInt(splittedTime[1].trim()) - dueDate.set(Calendar.HOUR_OF_DAY, hour) - dueDate.set(Calendar.MINUTE, minute) - dueDate.set(Calendar.SECOND, 0) - if (dueDate.before(currentDate)) - dueDate.add(Calendar.HOUR_OF_DAY, 24) - val timeDiff = dueDate.timeInMillis - currentDate.timeInMillis - + val timeDiff = runAt(hour, minute) val who = when (time) { - breakfastTime -> Who(Workers.BREAKFAST_UUID, Workers.BREAKFAST) - lunchTime -> Who(Workers.LUNCH_UUID, Workers.LUNCH) - dinnerTime -> Who(Workers.DINNER_UUID, Workers.DINNER) - else -> return // This should never happen + 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} at ${dueDate.time}" + "Scheduled activity ${who.uuid} in $timeDiff ms" ) - val workData = workDataOf( - Workers.WHO to who.id, - Workers.HOUR to hour, - Workers.MINUTE to minute - ) - val jobRequest = createJobRequest(timeDiff, workData) + val jobRequest = createJobRequest(timeDiff, who.clazz) val policy = if (forceUpdate) ExistingWorkPolicy.REPLACE @@ -91,27 +97,10 @@ class WorkHandler(private val context: Context) { } } - fun enqueueNotificationsWorker(delay: Long, data: Data) { - val jobRequest = createJobRequest(delay, data) - val who = when (data.getInt(Workers.WHO, -1)) { - Workers.BREAKFAST -> Workers.BREAKFAST_UUID - Workers.LUNCH -> Workers.LUNCH_UUID - Workers.DINNER -> Workers.DINNER_UUID - else -> return - } - Timber.d("Enqueuing job with ID: $who") - with(workManager) { - enqueueUniqueWork( - who, - ExistingWorkPolicy.APPEND, - jobRequest - ) - } - } - private fun createJobRequest( initialDelayMillis: Long, - inputData: Data + clazz: Class, + inputData: Data? = null ): OneTimeWorkRequest { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.NOT_REQUIRED) @@ -120,15 +109,16 @@ class WorkHandler(private val context: Context) { .setRequiresDeviceIdle(false) .setRequiresStorageNotLow(false) .build() - return OneTimeWorkRequestBuilder() - .setInitialDelay(initialDelayMillis, TimeUnit.MILLISECONDS) - .setInputData(inputData) - .setConstraints(constraints) - .setBackoffCriteria( + return with(OneTimeWorkRequest.Builder(clazz)) { + setInitialDelay(initialDelayMillis, TimeUnit.MILLISECONDS) + inputData?.let { setInputData(it) } + setConstraints(constraints) + setBackoffCriteria( BackoffPolicy.EXPONENTIAL, 15000L, TimeUnit.MILLISECONDS ) - .build() + build() + } } } \ No newline at end of file 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 a230b66..fb2eb75 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt @@ -18,20 +18,18 @@ */ package com.javinator9889.handwashingreminder.network -import com.javinator9889.handwashingreminder.utils.AndroidVersion -import com.javinator9889.handwashingreminder.utils.OkHttp -import com.javinator9889.handwashingreminder.utils.OkHttpLegacy -import com.javinator9889.handwashingreminder.utils.isAtLeast +import com.javinator9889.handwashingreminder.network.okhttp.OkHttpDownloader as Downloader object HttpDownloader { fun newInstance(): OkHttpDownloader { - val className = if (isAtLeast(AndroidVersion.LOLLIPOP)) + /*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 okHttpProvider.newInstance()*/ + return Downloader.newInstance() } } \ 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 new file mode 100644 index 0000000..763497d --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/okhttp/OkHttpDownloader.kt @@ -0,0 +1,49 @@ +/* + * 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 diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt index 386900f..f7aa96f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt @@ -37,9 +37,10 @@ class LogReportTree : Timber.Tree() { ) { when (priority) { Log.DEBUG, Log.VERBOSE -> return + Log.WARN -> crashlytics.log("W: $tag: $message") Log.ERROR -> { - crashlytics.log("E/$tag: $message"); - t?.let { crashlytics.recordException(it) } + crashlytics.log("E/$tag: $message") + t?.let { crashlytics.recordException(t) } } } } 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 e683dee..1bb51da 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt @@ -18,19 +18,52 @@ */ package com.javinator9889.handwashingreminder.utils -import android.annotation.SuppressLint -import java.text.SimpleDateFormat +import androidx.annotation.IntRange +import java.time.Duration +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.temporal.ChronoUnit import java.util.* +import kotlin.math.abs -@SuppressLint("SimpleDateFormat") -fun timeDifferenceSecs(to: String): Long { - if (to == "") return 0L - val dateFormat = SimpleDateFormat("HH:mm") - val fromDate = dateFormat.parse(to) ?: return 0L - val cTime = Calendar.getInstance().time - val diff = fromDate.time - cTime.time - dateFormat.parse(dateFormat.format(diff))?.let { return it.time / 1000 } - return 0L -} -fun formatTime(time: Int) = if (time < 10) "0$time" else time.toString() \ No newline at end of file +fun formatTime(time: Int) = if (time < 10) "0$time" else time.toString() + +fun runAt( + @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) + abs(Duration.between(LocalDateTime.now(), now).toMillis()) + } else { + // get now time and truncate it to minutes + val now = Calendar.getInstance() + // clone now time truncated to minutes and set the specified hour and + // minute in the new Calendar object + val alarm = Calendar.getInstance().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) + } + abs(alarm.timeInMillis - now.timeInMillis) + } + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e11aa59..a17df80 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -53,7 +53,7 @@ contenido a la aplicación para que funcione correctamente. Por favor, espera un minuto - este proceso no debería durar mucho. ¡Vaya! Algo ha ido - mal 😥 - intenta el proceso de nuevo + mal 😥 - intenta el proceso de nuevo | código de error: %1$d Hecho Preparando… Instalando… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fdfe0ae..8741206 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,7 +52,7 @@ content to the app so it works correctly. Please, wait a minute - this process should not last long. Oops! Something went - wrong 😥 - please retry the process + wrong 😥 - please retry the process | error code: %1$d Done Preparing… Installing… diff --git a/appintro/build.gradle b/appintro/build.gradle index 89274e3..5dbb6a6 100644 --- a/appintro/build.gradle +++ b/appintro/build.gradle @@ -71,7 +71,6 @@ dependencies { // 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' 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 3c13fd8..9b4275f 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -53,6 +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.listeners.ViewHolder import com.javinator9889.handwashingreminder.utils.* import kotlinx.android.synthetic.main.animated_intro.* @@ -72,7 +73,7 @@ class IntroActivity : AppIntro2(), override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) - SplitCompat.installActivity(base) + SplitCompat.installActivity(this) } override fun onCreate(savedInstanceState: Bundle?) { @@ -180,7 +181,9 @@ class IntroActivity : AppIntro2(), app.activityHandler.startTrackingActivity() else app.activityHandler.disableActivityTracker() - app.workHandler.enqueuePeriodicNotificationsWorker() + with(WorkHandler(this)) { + enqueuePeriodicNotificationsWorker() + } val firebaseAnalytics = FirebaseAnalytics.getInstance(this) with(Bundle(2)) { putBoolean( diff --git a/build.gradle b/build.gradle index 5da1a83..dad0e28 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,9 @@ buildscript { repositories { google() jcenter() - + maven { + url 'https://maven.fabric.io/public' + } } dependencies { classpath 'com.android.tools.build:gradle:3.6.3' diff --git a/bundledemoji/build.gradle b/bundledemoji/build.gradle index 3dc3fb8..80d84d4 100644 --- a/bundledemoji/build.gradle +++ b/bundledemoji/build.gradle @@ -25,9 +25,8 @@ dependencies { implementation 'androidx.emoji:emoji-appcompat:1.0.0' implementation 'androidx.emoji:emoji-bundled:1.0.0' implementation 'androidx.core:core-ktx:1.2.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // 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' diff --git a/secrets.properties b/secrets.properties new file mode 100644 index 0000000..1958fa6 --- /dev/null +++ b/secrets.properties @@ -0,0 +1,6 @@ +# (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" diff --git a/tgs b/tgs index b238b79..d2ddbad 160000 --- a/tgs +++ b/tgs @@ -1 +1 @@ -Subproject commit b238b796feed1b47ca0f926627305ce5ef866719 +Subproject commit d2ddbad8db2e29a090122f2a7da50aaa2ff2c3d2