diff --git a/README.md b/README.md index 712aa89..9a68990 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,10 @@ Currently, the application supports: + Send notifications at specific time. This feature was developed using the Android's - [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager). + [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) + but because the notifications were not working as expected (they were almost + always delayed) the app now uses AlarmManager for waking the device at + specific time, even if it is in Doze mode. + Detect user activity and send a notification when ends an specific one. For example, if he gets out of a vehicle, or has just finished running, etc. This was developed using Google's [Activity Recognition diff --git a/app/build.gradle b/app/build.gradle index 6ced094..b0c70fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 118 + versionCode 121 versionName "1.1.2-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -78,6 +78,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' multiDexEnabled = true signingConfig signingConfigs.release + versionNameSuffix "-stable" debuggable false jniDebuggable false @@ -171,7 +172,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.1' + api 'androidx.preference:preference-ktx:1.1.1' // https://github.com/bumptech/glide api 'com.github.bumptech.glide:glide:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0' 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 4c57aa7..67e11f0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -20,6 +20,7 @@ package com.javinator9889.handwashingreminder.activities import android.app.Activity import android.content.Intent +import android.content.SharedPreferences import android.os.Bundle import android.view.animation.Animation import android.view.animation.AnimationUtils @@ -27,6 +28,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenCreated import androidx.lifecycle.whenStarted +import androidx.preference.PreferenceManager import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.play.core.splitcompat.SplitCompat @@ -41,7 +43,6 @@ 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.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.* import com.javinator9889.handwashingreminder.utils.Preferences.Companion.ADS_ENABLED @@ -65,13 +66,16 @@ class LauncherActivity : AppCompatActivity() { private var launchOnInstall = false private var launchFromNotification = false private var canFinishActivity = false + private lateinit var sharedPreferences: SharedPreferences private lateinit var app: HandwashingApplication private lateinit var initDeferred: Deferred init { lifecycleScope.launch { whenCreated { - app = HandwashingApplication.getInstance() + app = HandwashingApplication.instance + sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this@LauncherActivity) with(intent) { notNull { launchFromNotification = @@ -141,7 +145,7 @@ class LauncherActivity : AppCompatActivity() { super.onActivityResult(requestCode, resultCode, data) if (requestCode == DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { EmojiLoader.get(this) - if (app.sharedPreferences.getBoolean(ADS_ENABLED, true)) { + if (sharedPreferences.getBoolean(ADS_ENABLED, true)) { when (resultCode) { Activity.RESULT_OK -> { initAds() @@ -183,9 +187,9 @@ class LauncherActivity : AppCompatActivity() { private fun installRequiredModules() { val modules = ArrayList(MODULE_COUNT) val googleApi = GoogleApiAvailability.getInstance() - if (app.sharedPreferences.getBoolean(ADS_ENABLED, true)) + if (sharedPreferences.getBoolean(ADS_ENABLED, true)) modules += Ads.MODULE_NAME - if (!app.sharedPreferences.getBoolean(APP_INIT_KEY, false)) { + if (!sharedPreferences.getBoolean(APP_INIT_KEY, false)) { modules += AppIntro.MODULE_NAME launchOnInstall = true } @@ -247,7 +251,7 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Setting-up security providers") Security.insertProviderAt(Conscrypt.newProvider(), 1) Timber.d("Setting-up activity recognition") - if (app.sharedPreferences.getBoolean( + if (sharedPreferences.getBoolean( Preferences.ACTIVITY_TRACKING_ENABLED, false ) && with(GoogleApiAvailability.getInstance()) { isGooglePlayServicesAvailable(this@LauncherActivity) == @@ -258,8 +262,6 @@ class LauncherActivity : AppCompatActivity() { } else { app.activityHandler.disableActivityTracker() } - Timber.d("Initializing Billing Service") - app.billingService = BillingService(this) with(AlarmHandler(this)) { scheduleAllAlarms() } @@ -306,13 +308,13 @@ class LauncherActivity : AppCompatActivity() { } } firebaseAnalytics.setAnalyticsCollectionEnabled( - app.sharedPreferences.getBoolean( + sharedPreferences.getBoolean( Preferences.ANALYTICS_ENABLED, true ) ) firebasePerformance.isPerformanceCollectionEnabled = - app.sharedPreferences.getBoolean( + sharedPreferences.getBoolean( Preferences.PERFORMANCE_ENABLED, true ) 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 b2a1b8a..1219b03 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -125,9 +125,14 @@ class MainActivity : ActionBarBase(), menu.selectedItemId = R.id.diseases onNavigationItemSelected(menu.menu.findItem(R.id.diseases)) } else { - if (activeFragment == R.id.diseases) + if (activeFragment == R.id.diseases) { + with(fragments[activeFragment].get()!! as DiseasesFragment) { + onBackPressed() + } + fragments.clear() super.onBackPressed() - else { + finish() + } else { val washingHandsFragment = fragments[activeFragment].get() ?: createFragmentForId(R.id.handwashing) as WashingHandsFragment diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseDescriptionFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt similarity index 57% rename from app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseDescriptionFragment.kt rename to app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt index 6492267..d2130dc 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseDescriptionFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt @@ -16,7 +16,7 @@ * * Created by Javinator9889 on 19/04/20 - Handwashing reminder. */ -package com.javinator9889.handwashingreminder.collections +package com.javinator9889.handwashingreminder.activities.views.fragments.diseases import android.os.Bundle import androidx.annotation.LayoutRes @@ -24,19 +24,26 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText import kotlinx.android.synthetic.main.disease_description.* +import kotlin.properties.Delegates internal const val ARG_TITLE = "bundle:title" internal const val ARG_SDESC = "bundle:description:short" internal const val ARG_LDESC = "bundle:description:long" internal const val ARG_PROVIDER = "bundle:provider" internal const val ARG_WEBSITE = "bundle:website" +internal const val ARG_ANIMATION_ID = "bundle:animation:id" +internal const val ARG_HTML_TEXT = "bundle:text:html" -class DiseaseDescriptionFragment( - private val parsedHTMLText: ParsedHTMLText, - private val animId: Int -) : BaseFragmentView() { +class DiseaseDescriptionFragment : BaseFragmentView() { @get:LayoutRes override val layoutId: Int = R.layout.disease_description + private lateinit var parsedHTMLText: ParsedHTMLText + private var animId by Delegates.notNull() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + retainInstance = true + } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -45,26 +52,26 @@ class DiseaseDescriptionFragment( outState.putCharSequence(ARG_LDESC, longDescription.text) outState.putCharSequence(ARG_PROVIDER, provider.text) outState.putCharSequence(ARG_WEBSITE, website.text) + outState.putParcelable(ARG_HTML_TEXT, parsedHTMLText) + outState.putInt(ARG_ANIMATION_ID, animId) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - animatedView.setAnimation(animId) - if (savedInstanceState != null) { - title.text = savedInstanceState.getCharSequence(ARG_TITLE) - shortDescription.text = - savedInstanceState.getCharSequence(ARG_SDESC) - longDescription.text = savedInstanceState.getCharSequence(ARG_LDESC) - provider.text = savedInstanceState.getCharSequence(ARG_PROVIDER) - website.text = savedInstanceState.getCharSequence(ARG_WEBSITE) - } else { - title.text = parsedHTMLText.name - shortDescription.text = parsedHTMLText.shortDescription - longDescription.text = parsedHTMLText.longDescription - provider.text = - getString(R.string.written_by, parsedHTMLText.provider) - website.text = - getString(R.string.available_at, parsedHTMLText.website) + if (savedInstanceState != null || arguments != null) { + val data = (savedInstanceState ?: arguments)!! + parsedHTMLText = data.getParcelable(ARG_HTML_TEXT)!! + animId = data.getInt(ARG_ANIMATION_ID) + animatedView.setAnimation(animId) + title.text = data.getCharSequence(ARG_TITLE) ?: parsedHTMLText.name + shortDescription.text = data.getCharSequence(ARG_SDESC) + ?: parsedHTMLText.shortDescription + longDescription.text = data.getCharSequence(ARG_LDESC) + ?: parsedHTMLText.longDescription + provider.text = data.getCharSequence(ARG_PROVIDER) + ?: getString(R.string.written_by, parsedHTMLText.provider) + website.text = data.getCharSequence(ARG_WEBSITE) + ?: getString(R.string.available_at, parsedHTMLText.website) } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseExtraInformationFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt similarity index 63% rename from app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseExtraInformationFragment.kt rename to app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt index 0f8024b..634879e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseExtraInformationFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt @@ -16,7 +16,7 @@ * * Created by Javinator9889 on 19/04/20 - Handwashing reminder. */ -package com.javinator9889.handwashingreminder.collections +package com.javinator9889.handwashingreminder.activities.views.fragments.diseases import android.os.Bundle import androidx.annotation.LayoutRes @@ -24,16 +24,22 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText import kotlinx.android.synthetic.main.simple_text_view.* +import kotlin.properties.Delegates internal const val ARG_SYMPTOMS = "bundle:symptoms" internal const val ARG_PREVENTION = "bundle:prevention" +internal const val ARG_POSITION = "bundle:item:position" -class DiseaseExtraInformationFragment( - private val position: Int, - private val parsedHTMLText: ParsedHTMLText -) : BaseFragmentView() { +class DiseaseExtraInformationFragment : BaseFragmentView() { @get:LayoutRes override val layoutId: Int = R.layout.simple_text_view + private var position by Delegates.notNull() + private lateinit var parsedHTMLText: ParsedHTMLText + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + retainInstance = true + } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -41,25 +47,23 @@ class DiseaseExtraInformationFragment( outState.putCharSequence(ARG_SYMPTOMS, text.text) else if (position == 2) outState.putCharSequence(ARG_PREVENTION, text.text) + outState.putInt(ARG_POSITION, position) + outState.putParcelable(ARG_HTML_TEXT, parsedHTMLText) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - if (savedInstanceState != null) - text.text = when (position) { - 1 -> savedInstanceState.getCharSequence( - ARG_SYMPTOMS - ) - 2 -> savedInstanceState.getCharSequence( - ARG_PREVENTION - ) - else -> "" - } - else + if (savedInstanceState != null || arguments != null) { + val data = (savedInstanceState ?: arguments)!! + position = data.getInt(ARG_POSITION) + parsedHTMLText = data.getParcelable(ARG_HTML_TEXT)!! text.text = when (position) { - 1 -> parsedHTMLText.symptoms - 2 -> parsedHTMLText.prevention + 1 -> data.getCharSequence(ARG_SYMPTOMS) + ?: parsedHTMLText.symptoms + 2 -> data.getCharSequence(ARG_PREVENTION) + ?: parsedHTMLText.prevention else -> "" } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index a396383..4284573 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -43,6 +43,7 @@ import com.mikepenz.fastadapter.listeners.ClickEventHook import kotlinx.android.synthetic.main.diseases_list.* import kotlinx.android.synthetic.main.diseases_list.view.* import kotlinx.coroutines.launch +import timber.log.Timber class DiseasesFragment : BaseFragmentView() { override val layoutId: Int = R.layout.diseases_list @@ -93,6 +94,23 @@ class DiseasesFragment : BaseFragmentView() { adapter = fastAdapter } fastAdapter.addEventHook(DiseaseClickEventHook()) + fastAdapter.withSavedInstanceState(savedInstanceState) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + fastAdapter.saveInstanceState(outState) + } + + fun onBackPressed() { + try { + diseasesContainer.adapter = null + diseasesAdapter.clear() + } catch (e: Exception) { + Timber.w(e, "Exception when calling 'onBackPressed'") + } finally { + onDestroy() + } } private inner class DiseaseClickEventHook : ClickEventHook() { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt index f00e806..5849a81 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt @@ -33,7 +33,7 @@ class Ads : AbstractItem() { override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) class ViewHolder(v: View) : FastAdapter.ViewHolder(v) { - private val ads = HandwashingApplication.getInstance().adLoader + private val ads = HandwashingApplication.instance.adLoader private val container = v.findViewById(R.id.adsContainer) override fun bindView(item: Ads, payloads: List) { 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 aa3923e..b2c87ee 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 @@ -89,7 +89,7 @@ class ActivityCheckbox : CheckBoxPreference { firstCheck = false return } - with(HandwashingApplication.getInstance()) { + with(HandwashingApplication.instance) { if (checked) { activityHandler.startTrackingActivity() } else { 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 374d356..8d0db50 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 @@ -82,7 +82,7 @@ class ActivityMultiSelectList : MultiSelectListPreference { } private fun reloadActivityHandler() { - with(HandwashingApplication.getInstance()) { + with(HandwashingApplication.instance) { activityHandler.reload() } } 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 ad9823c..a7412f5 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.gms.vendor.BillingService import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.listeners.OnPurchaseFinishedListener import com.javinator9889.handwashingreminder.utils.* @@ -49,7 +50,7 @@ import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import timber.log.Timber import java.lang.ref.WeakReference @@ -62,7 +63,8 @@ class SettingsView : PreferenceFragmentCompat(), private lateinit var adsPreference: WeakReference private lateinit var donationsPreference: WeakReference private lateinit var emojiCompat: EmojiCompat - private val app = HandwashingApplication.getInstance() + private lateinit var billingService: BillingService + private val app = HandwashingApplication.instance override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -78,6 +80,7 @@ class SettingsView : PreferenceFragmentCompat(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + billingService = BillingService(view.context) viewLifecycleOwner.lifecycleScope.launch { val emojiLoader = EmojiLoader.get(view.context) val share = findPreference("share") @@ -108,227 +111,277 @@ class SettingsView : PreferenceFragmentCompat(), val suggestions = findPreference("send_suggestions") val libraries = findPreference("opensource_libs") val privacyAndTerms = findPreference("tos_privacy") - share?.let { - it.icon = icon(Ionicons.Icon.ion_android_share) - it.setOnPreferenceClickListener { - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent(FirebaseAnalytics.Event.SHARE, null) - } - with(Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - putExtra( - Intent.EXTRA_TEXT, - getText(R.string.share_text) - ) - putExtra( - Intent.EXTRA_TITLE, - getText(R.string.share_title) - ) - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo - ) - ) - clipData = ClipData( - ClipDescription( - getString(R.string.share_label), - arrayOf("image/*") - ), - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo + val deferreds = mutableListOf>() + deferreds.add( + async(Dispatchers.Main) { + share?.let { + it.icon = icon(Ionicons.Icon.ion_android_share) + it.setOnPreferenceClickListener { + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent(FirebaseAnalytics.Event.SHARE, null) + } + with(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + putExtra( + Intent.EXTRA_TEXT, + getText(R.string.share_text) ) - ) - ) - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - type = "text/plain" - }, null)) { - startActivity(this) - } - true - } - } - playStore?.let { - it.icon = icon(Ionicons.Icon.ion_android_playstore) - it.setOnPreferenceClickListener { - openWebsite(PLAYSTORE_URL, R.string.playstore_err) - true - } - } - telegram?.let { - it.setOnPreferenceClickListener { - openWebsite(TELEGRAM_URL, R.string.telegram_err) - true - } - } - github?.let { - it.icon = icon(Ionicons.Icon.ion_social_github) - it.setOnPreferenceClickListener { - openWebsite(GITHUB_URL, R.string.browser_err) - true - } - } - twitter?.let { - it.icon = icon(Ionicons.Icon.ion_social_twitter) - it.setOnPreferenceClickListener { - openWebsite(TWITTER_URL, R.string.twitter_err) - true - } - } - linkedIn?.let { - it.icon = icon(Ionicons.Icon.ion_social_linkedin) - it.setOnPreferenceClickListener { - openWebsite(LINKEDIN_URL, R.string.browser_err) - true - } - } - firebaseAnalytics?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_arrow_graph_up_right) - firebaseAnalyticsPreference = WeakReference(it) - } - firebasePerformance?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_speedometer) - firebasePerformancePreference = WeakReference(it) - } - ads?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_ios_barcode_outline) - adsPreference = WeakReference(it) - } - donations?.let { - it.onPreferenceChangeListener = this@SettingsView - it.entryValues = if (isDebuggable()) - resources.getTextArray(R.array.in_app_donations_debug) - else - resources.getTextArray(R.array.in_app_donations) - it.icon = icon(Ionicons.Icon.ion_card) - app.billingService - .addOnPurchaseFinishedListener(this@SettingsView) - donationsPreference = WeakReference(it) - } - translations?.let { - it.icon = icon(Ionicons.Icon.ion_chatbox_working) - it.setOnPreferenceClickListener { - openWebsite(TRANSLATE_URL, R.string.browser_err) - true - } - } - suggestions?.let { - it.setOnPreferenceClickListener { - with(Intent(Intent.ACTION_SENDTO)) { - type = "*/*" - data = Uri.parse("mailto:") - putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) - putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) - putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) - if (resolveActivity(requireContext().packageManager) - != null - ) { - startActivity(this) - } else { - MaterialDialog(requireContext()).show { - title(R.string.no_app) - message( - text = getString( - R.string.no_app_long, - getString(R.string.sending_email) + putExtra( + Intent.EXTRA_TITLE, + getText(R.string.share_title) + ) + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) + ) + clipData = ClipData( + ClipDescription( + getString(R.string.share_label), + arrayOf("image/*") + ), + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) ) ) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + type = "text/plain" + }, null)) { + startActivity(this) } + true } } - true - } - it.icon = icon(Ionicons.Icon.ion_chatbubbles) - } - libraries?.let { - it.setOnPreferenceClickListener { - val bundle = Bundle(1).apply { - putString("view", "libs") + }) + deferreds.add( + async(Dispatchers.Main) { + playStore?.let { + it.icon = icon(Ionicons.Icon.ion_android_playstore) + it.setOnPreferenceClickListener { + openWebsite(PLAYSTORE_URL, R.string.playstore_err) + true + } } - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) + }) + deferreds.add( + async(Dispatchers.Main) { + telegram?.let { + it.setOnPreferenceClickListener { + openWebsite(TELEGRAM_URL, R.string.telegram_err) + true + } } - LibsBuilder() - .withAutoDetect(true) - .withFields(R.string::class.java.fields) - .withCheckCachedDetection(true) - .withSortEnabled(true) - .withAboutVersionShown(true) - .withAboutVersionShownCode(true) - .withAboutVersionShownName(true) - .withShowLoadingProgress(true) - .withActivityTitle(getString(R.string.app_name)) - .start(requireContext()) - true - } - it.icon = icon(Ionicons.Icon.ion_code) - } - privacyAndTerms?.let { - it.setOnPreferenceClickListener { - Intent( - requireContext(), - PrivacyTermsActivity::class.java - ).run { - startActivity(this) + }) + deferreds.add( + async(Dispatchers.Main) { + github?.let { + it.icon = icon(Ionicons.Icon.ion_social_github) + it.setOnPreferenceClickListener { + openWebsite(GITHUB_URL, R.string.browser_err) + true + } } - true - } - it.icon = icon(Ionicons.Icon.ion_android_cloud_done) - } - 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)) - it.summaryText = - emojiCompat.process(getText(R.string.breakfast_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.breakfast_pref_title) - it.summaryText = getText(R.string.breakfast_pref_summ) - } finally { - it.updateSummary() - } - } - 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)) - it.summaryText = - emojiCompat.process(getText(R.string.lunch_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.lunch_pref_title) - it.summaryText = getText(R.string.lunch_pref_summ) - } finally { - it.updateSummary() - } - } - 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)) - it.summaryText = - emojiCompat.process(getText(R.string.dinner_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.dinner_pref_title) - it.summaryText = getText(R.string.dinner_pref_summ) - } finally { - it.updateSummary() - } - } + }) + deferreds.add( + async(Dispatchers.Main) { + twitter?.let { + it.icon = icon(Ionicons.Icon.ion_social_twitter) + it.setOnPreferenceClickListener { + openWebsite(TWITTER_URL, R.string.twitter_err) + true + } + } + }) + deferreds.add( + async(Dispatchers.Main) { + linkedIn?.let { + it.icon = icon(Ionicons.Icon.ion_social_linkedin) + it.setOnPreferenceClickListener { + openWebsite(LINKEDIN_URL, R.string.browser_err) + true + } + } + }) + deferreds.add( + async(Dispatchers.Main) { + firebaseAnalytics?.let { + it.onPreferenceChangeListener = this@SettingsView + it.icon = icon(Ionicons.Icon.ion_arrow_graph_up_right) + firebaseAnalyticsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + firebasePerformance?.let { + it.onPreferenceChangeListener = this@SettingsView + it.icon = icon(Ionicons.Icon.ion_speedometer) + firebasePerformancePreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + ads?.let { + it.onPreferenceChangeListener = this@SettingsView + it.icon = icon(Ionicons.Icon.ion_ios_barcode_outline) + adsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + donations?.let { + it.onPreferenceChangeListener = this@SettingsView + it.entryValues = if (isDebuggable()) + resources.getTextArray(R.array.in_app_donations_debug) + else + resources.getTextArray(R.array.in_app_donations) + it.icon = icon(Ionicons.Icon.ion_card) + billingService.addOnPurchaseFinishedListener(this@SettingsView) + donationsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + translations?.let { + it.icon = icon(Ionicons.Icon.ion_chatbox_working) + it.setOnPreferenceClickListener { + openWebsite(TRANSLATE_URL, R.string.browser_err) + true + } + } + }) + deferreds.add( + async(Dispatchers.Main) { + suggestions?.let { + it.setOnPreferenceClickListener { + with(Intent(Intent.ACTION_SENDTO)) { + type = "*/*" + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) + putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) + putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) + if (resolveActivity(requireContext().packageManager) + != null + ) { + startActivity(this) + } else { + MaterialDialog(requireContext()).show { + title(R.string.no_app) + message( + text = getString( + R.string.no_app_long, + getString(R.string.sending_email) + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + } + true + } + it.icon = icon(Ionicons.Icon.ion_chatbubbles) + } + }) + deferreds.add( + async(Dispatchers.Main) { + libraries?.let { + it.setOnPreferenceClickListener { + val bundle = Bundle(1).apply { + putString("view", "libs") + } + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent( + FirebaseAnalytics.Event.VIEW_ITEM, + bundle + ) + } + LibsBuilder() + .withAutoDetect(true) + .withFields(R.string::class.java.fields) + .withCheckCachedDetection(true) + .withSortEnabled(true) + .withAboutVersionShown(true) + .withAboutVersionShownCode(true) + .withAboutVersionShownName(true) + .withShowLoadingProgress(true) + .withActivityTitle(getString(R.string.app_name)) + .start(requireContext()) + true + } + it.icon = icon(Ionicons.Icon.ion_code) + } + }) + deferreds.add( + async(Dispatchers.Main) { + privacyAndTerms?.let { + it.setOnPreferenceClickListener { + Intent( + requireContext(), + PrivacyTermsActivity::class.java + ).run { + startActivity(this) + } + true + } + it.icon = icon(Ionicons.Icon.ion_android_cloud_done) + } + }) + deferreds.add( + async(Dispatchers.Main) { + 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)) + it.summaryText = + emojiCompat.process(getText(R.string.breakfast_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.breakfast_pref_title) + it.summaryText = + getText(R.string.breakfast_pref_summ) + } finally { + it.updateSummary() + } + } + 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)) + it.summaryText = + emojiCompat.process(getText(R.string.lunch_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.lunch_pref_title) + it.summaryText = getText(R.string.lunch_pref_summ) + } finally { + it.updateSummary() + } + } + 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)) + it.summaryText = + emojiCompat.process(getText(R.string.dinner_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.dinner_pref_title) + it.summaryText = getText(R.string.dinner_pref_summ) + } finally { + it.updateSummary() + } + } + }) + deferreds.awaitAll() } } @@ -402,7 +455,7 @@ class SettingsView : PreferenceFragmentCompat(), Timber.d("Purchase clicked - $newValue") val purchaseId = newValue as String if (isConnected()) - app.billingService.doPurchase(purchaseId, requireActivity()) + billingService.doPurchase(purchaseId, requireActivity()) else { if (context == null) return false 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 d025e8f..09c6ed2 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 @@ -48,8 +48,7 @@ private const val LIVEDATA_KEY = "videomodel:livedata" class VideoModel( private val state: SavedStateHandle, private val position: Int ) : ViewModel() { - private val cachePath: File = - HandwashingApplication.getInstance().applicationContext.cacheDir + private val cachePath: File = HandwashingApplication.instance.applicationContext.cacheDir val videos: LiveData = liveData { emitSource(state.getLiveData(LIVEDATA_KEY, loadVideo())) } 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 afa4234..79d2c19 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 @@ -76,7 +76,7 @@ class WashingHandsModel( } private suspend fun processStringArray(@ArrayRes array: Int): CharSequence = - with(HandwashingApplication.getInstance()) { + with(HandwashingApplication.instance) { with(EmojiLoader.get(this)) { try { this.await() 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 8b2136f..0b192d7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -27,7 +27,6 @@ import com.google.firebase.FirebaseApp 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.utils.LogReportTree import com.javinator9889.handwashingreminder.utils.isDebuggable import javinator9889.localemanager.application.BaseApplication @@ -38,17 +37,11 @@ import timber.log.Timber class HandwashingApplication : BaseApplication() { var adLoader: AdLoader? = null - lateinit var billingService: BillingService lateinit var activityHandler: ActivityHandler - lateinit var sharedPreferences: SharedPreferences lateinit var firebaseInitDeferred: Deferred companion object { - private lateinit var instance: HandwashingApplication - - fun getInstance(): HandwashingApplication { - return this.instance - } + lateinit var instance: HandwashingApplication } override fun attachBaseContext(base: Context?) { @@ -63,7 +56,6 @@ class HandwashingApplication : BaseApplication() { override fun onCreate() { super.onCreate() instance = this - sharedPreferences = getCustomSharedPreferences(this) activityHandler = ActivityHandler(this) firebaseInitDeferred = initFirebaseAppAsync() } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt index 475579b..44b1ddc 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt @@ -18,22 +18,33 @@ */ package com.javinator9889.handwashingreminder.collections +import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.ARG_ANIMATION_ID +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.ARG_HTML_TEXT +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.ARG_POSITION +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseaseDescriptionFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseaseExtraInformationFragment import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText class DiseaseTextAdapter( fm: FragmentActivity, private val animId: Int, private val parsedHTMLText: ParsedHTMLText -) : - FragmentStateAdapter(fm) { +) : FragmentStateAdapter(fm) { override fun getItemCount(): Int = 3 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> DiseaseDescriptionFragment(parsedHTMLText, animId) - 1, 2 -> DiseaseExtraInformationFragment(position, parsedHTMLText) + 0 -> DiseaseDescriptionFragment() + 1, 2 -> DiseaseExtraInformationFragment() else -> Fragment() + }.apply { + val bundle = Bundle(3) + bundle.putInt(ARG_POSITION, position) + bundle.putInt(ARG_ANIMATION_ID, animId) + bundle.putParcelable(ARG_HTML_TEXT, parsedHTMLText) + arguments = bundle } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/PrivacyTermsCollectionAdapter.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/PrivacyTermsCollectionAdapter.kt index d11d94c..d10d587 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/PrivacyTermsCollectionAdapter.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/PrivacyTermsCollectionAdapter.kt @@ -46,7 +46,7 @@ class PrivacyTermsCollectionAdapter(fm: FragmentActivity) : } -private const val ARG_POSITION = "item:position" +internal const val ARG_POSITION = "item:position" class PolicyTextViewFragment : BaseFragment() { override fun onCreateView( 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 86c662e..1a614c9 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt @@ -21,6 +21,7 @@ package com.javinator9889.handwashingreminder.jobs import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.preference.PreferenceManager import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.Preferences @@ -29,8 +30,8 @@ import timber.log.Timber class BootCompletedJob : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - val app = HandwashingApplication.getInstance() - val preferences = app.sharedPreferences + val app = HandwashingApplication.instance + val preferences = PreferenceManager.getDefaultSharedPreferences(context) if (preferences.getBoolean( Preferences.ACTIVITY_TRACKING_ENABLED, false 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 6335e8f..10d633c 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 @@ -72,7 +72,7 @@ abstract class ScheduledNotificationWorker(context: Context) { .currentTimeMillis() - startTime}ms" ) } catch (e: Exception) { - with(HandwashingApplication.getInstance()) { + with(HandwashingApplication.instance) { // 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 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 7e0a8d7..eadf927 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt @@ -45,7 +45,7 @@ fun isAtLeast(version: AndroidVersion): Boolean = Build.VERSION.SDK_INT >= version.code fun isHighPerformingDevice(): Boolean { - with(HandwashingApplication.getInstance()) { + with(HandwashingApplication.instance) { val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val isLowRamDevice = @@ -59,7 +59,7 @@ fun isHighPerformingDevice(): Boolean { } fun isConnected(): Boolean { - val connectivityManager = HandwashingApplication.getInstance() + val connectivityManager = HandwashingApplication.instance .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager if (isAtLeast(AndroidVersion.M)) { val network = connectivityManager.activeNetwork ?: return false @@ -79,7 +79,7 @@ fun isConnected(): Boolean { } fun isDebuggable(): Boolean = - (0 != HandwashingApplication.getInstance().applicationInfo.flags and + (0 != HandwashingApplication.instance.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) fun getDeviceInfo(): String = with(StringBuilder()) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Graphics.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Graphics.kt index 49a992d..80cdf8b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Graphics.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Graphics.kt @@ -27,7 +27,7 @@ import com.javinator9889.handwashingreminder.application.HandwashingApplication * @return A float value to represent px equivalent to dp depending on device density */ fun dpToPx(dp: Float): Float { - val context = HandwashingApplication.getInstance().applicationContext + val context = HandwashingApplication.instance.applicationContext return dp * context.resources.displayMetrics.density } @@ -38,6 +38,6 @@ fun dpToPx(dp: Float): Float { * @return A float value to represent dp equivalent to px value */ fun pxToDp(px: Float): Float { - val context = HandwashingApplication.getInstance().applicationContext + val context = HandwashingApplication.instance.applicationContext return px / context.resources.displayMetrics.density } \ No newline at end of file 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 39b6830..002178f 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -30,6 +30,7 @@ import androidx.annotation.Keep import androidx.core.content.edit import androidx.core.util.set import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager import com.github.paolorotolo.appintro.AppIntro2 import com.github.paolorotolo.appintro.AppIntroViewPager import com.google.android.gms.common.ConnectionResult.SUCCESS @@ -165,8 +166,8 @@ class IntroActivity : AppIntro2(), override fun onDonePressed(currentFragment: Fragment?) { super.onDonePressed(currentFragment) - val app = HandwashingApplication.getInstance() - val sharedPreferences = app.sharedPreferences + val app = HandwashingApplication.instance + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) sharedPreferences.edit(commit = true) { timeConfigSlide.itemAdapter.adapterItems.forEach { item -> val time = "${item.hours}:${item.minutes}"