From b7c4047f1d0104e64e34d835907ee8ae44a1ba6f Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 19 Dec 2023 23:08:38 +0100 Subject: [PATCH] Progress bars subscription groups, Various notification permission requests, improved notification experience, Settings activity open to specific settings, refs --- .../java/com/futo/platformplayer/Settings.kt | 2 +- .../futo/platformplayer/UISlideOverlays.kt | 30 ++++++++++- .../platformplayer/activities/MainActivity.kt | 31 ++++++++++++ .../activities/SettingsActivity.kt | 50 +++++++++++++++++++ .../main/SubscriptionsFeedFragment.kt | 22 +++++--- .../states/StateSubscriptions.kt | 6 ++- .../platformplayer/views/fields/FieldForm.kt | 4 ++ app/src/unstable/assets/sources/nebula | 2 +- 8 files changed, 134 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 6237f56c..f2d689ed 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -277,7 +277,7 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 9) var fetchOnTabOpen: Boolean = true; - @FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 10) + @FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 10, "background_update") @DropdownFieldOptionsId(R.array.background_interval) var subscriptionsBackgroundUpdateInterval: Int = 0; diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index bac10f4a..671dcb65 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -1,9 +1,14 @@ package com.futo.platformplayer +import android.app.Activity +import android.app.NotificationManager import android.content.ContentResolver +import android.content.Context +import android.content.Intent import android.view.View import android.view.ViewGroup import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.activities.SettingsActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.channels.IPlatformChannel @@ -131,8 +136,29 @@ class UISlideOverlays { subscription.save(); menu.hide(true); - if(subscription.doNotifications && !originalNotif && Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval == 0) { - UIDialogs.toast(container.context, "Enable 'Background Update' in settings for notifications to work"); + if(subscription.doNotifications && !originalNotif) { + val mainContext = StateApp.instance.contextOrNull; + if(Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval == 0) { + UIDialogs.toast(container.context, "Enable 'Background Update' in settings for notifications to work"); + + if(mainContext is MainActivity) { + UIDialogs.showDialog(mainContext, R.drawable.ic_settings, "Background Updating Required", + "You need to set a Background Updating interval for notifications", null, 0, + UIDialogs.Action("Cancel", {}), + UIDialogs.Action("Configure", { + val intent = Intent(mainContext, SettingsActivity::class.java); + intent.putExtra("query", mainContext.getString(R.string.background_update)); + mainContext.startActivity(intent); + }, UIDialogs.ActionStyle.PRIMARY)); + } + return@subscribe; + } + else if(!(mainContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled()) { + UIDialogs.toast(container.context, "Android notifications are disabled"); + if(mainContext is MainActivity) { + mainContext.requestNotificationPermissions("Notifications are required for subscription updating and notifications to work"); + } + } } }; menu.onCancel.subscribe { diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 35e5debf..2e1f2109 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -1,10 +1,12 @@ package com.futo.platformplayer.activities import android.annotation.SuppressLint +import android.app.NotificationManager import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.ActivityInfo +import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri import android.os.Bundle @@ -17,6 +19,8 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @@ -1009,6 +1013,33 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { } + val notifPermission = "android.permission.POST_NOTIFICATIONS"; + val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) + UIDialogs.toast(this, "Notification permission granted"); + else + UIDialogs.toast(this, "Notification permission denied"); + } + fun requestNotificationPermissions(reason: String) { + when { + ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> { + + } + ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> { + UIDialogs.showDialog(this, R.drawable.ic_notifications, "Notifications Required", + reason, null, 0, + UIDialogs.Action("Cancel", {}), + UIDialogs.Action("Enable", { + requestPermissionLauncher.launch(notifPermission); + }, UIDialogs.ActionStyle.PRIMARY)); + } + else -> { + requestPermissionLauncher.launch(notifPermission); + } + } + } + + //TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers. private var resultLauncherMap = mutableMapOfUnit>(); diff --git a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt index a2aa5023..268bd2c3 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt @@ -1,8 +1,10 @@ package com.futo.platformplayer.activities import android.annotation.SuppressLint +import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.widget.FrameLayout @@ -12,6 +14,8 @@ import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.* import com.futo.platformplayer.logging.Logger @@ -33,6 +37,14 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { lateinit var overlay: FrameLayout; + val notifPermission = "android.permission.POST_NOTIFICATIONS"; + val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) + UIDialogs.toast(this, "Notification permission granted"); + else + UIDialogs.toast(this, "Notification permission denied"); + } + override fun attachBaseContext(newBase: Context?) { Logger.i("SettingsActivity", "SettingsActivity.attachBaseContext") super.attachBaseContext(StateApp.instance.getLocaleContext(newBase)) @@ -58,6 +70,33 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { Logger.i("SettingsActivity", "App language change detected, propogating to shared preferences"); StateApp.instance.setLocaleSetting(this, Settings.instance.language.getAppLanguageLocaleString()); } + + if(field.descriptor?.id == "background_update") { + Logger.i("SettingsActivity", "Detected change in background work ${field.value}"); + if(Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval > 0) { + val notifManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + if(!notifManager.areNotificationsEnabled()) { + UIDialogs.toast(this, "Notifications aren't enabled"); + + when { + ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> { + + } + ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> { + UIDialogs.showDialog(this, R.drawable.ic_notifications, "Notifications Required", + "Notifications need to be enabled for background updating to function", null, 0, + UIDialogs.Action("Cancel", {}), + UIDialogs.Action("Enable", { + requestPermissionLauncher.launch(notifPermission); + }, UIDialogs.ActionStyle.PRIMARY)); + } + else -> { + requestPermissionLauncher.launch(notifPermission); + } + } + } + } + } }; _buttonBack.setOnClickListener { finish(); @@ -72,7 +111,10 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { reloadSettings(); } + var isFirstLoad = true; fun reloadSettings() { + val firstLoad = isFirstLoad; + isFirstLoad = false; _form.setSearchVisible(false); _loaderView.start(); _form.fromObject(lifecycleScope, Settings.instance) { @@ -90,6 +132,13 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { UIDialogs.toast(this, getString(R.string.you_are_now_in_developer_mode)); } }; + + if(firstLoad) { + val query = intent.getStringExtra("query"); + if(!query.isNullOrEmpty()) { + _form.setSearchQuery(query); + } + } }; } @@ -135,6 +184,7 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { resultLauncher.launch(intent); } + companion object { //TODO: Temporary for solving Settings issues @SuppressLint("StaticFieldLeak") diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index 1d078590..b466c5ac 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -104,14 +104,17 @@ class SubscriptionsFeedFragment : MainFragment() { constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) { Logger.i(TAG, "SubscriptionsFeedFragment constructor()"); - StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total -> - fragment.lifecycleScope.launch(Dispatchers.Main) { - try { - setProgress(progress, total); - } catch (e: Throwable) { - Logger.e(TAG, "Failed to set progress", e); + StateSubscriptions.instance.onFeedProgress.subscribe(this) { id, progress, total -> + if(_subGroup?.id == id) + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + setProgress(progress, total); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to set progress", e); + } } - } + } + StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total -> }; StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { _, added -> @@ -264,7 +267,10 @@ class SubscriptionsFeedFragment : MainFragment() { UISlideOverlays.showCreateSubscriptionGroup(_overlayContainer); else { _subGroup = g; - loadCache(); //TODO: Proper subset update + setProgress(0, 0); + if(Settings.instance.subscriptions.fetchOnTabOpen) + loadResults(false); + else loadCache(); } }; _subscriptionBar?.onHoldGroup?.subscribe { g -> diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt index 9af9d2b6..04fd5c70 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -9,6 +9,7 @@ import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.structures.* import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.constructs.Event3 import com.futo.platformplayer.functional.CentralizedFeed import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription @@ -50,7 +51,7 @@ class StateSubscriptions { val global: CentralizedFeed = CentralizedFeed(); val feeds: HashMap = hashMapOf(); - + val onFeedProgress = Event3(); val onSubscriptionsChanged = Event2, Boolean>(); @@ -70,6 +71,9 @@ class StateSubscriptions { var f = feeds[id]; if(f == null && createIfNew) { f = CentralizedFeed(); + f.onUpdateProgress.subscribe { progress, total -> + onFeedProgress.emit(id, progress, total) + }; feeds[id] = f; } return@synchronized f; diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt index e5b73757..8412c98e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt @@ -71,6 +71,10 @@ class FieldForm : LinearLayout { } } + fun setSearchQuery(query: String) { + _editSearch.setText(query); + updateSettingsVisibility(); + } fun setSearchVisible(visible: Boolean) { _containerSearch.visibility = if(visible) View.VISIBLE else View.GONE; _editSearch.setText(""); diff --git a/app/src/unstable/assets/sources/nebula b/app/src/unstable/assets/sources/nebula index 863d0be1..01270edb 160000 --- a/app/src/unstable/assets/sources/nebula +++ b/app/src/unstable/assets/sources/nebula @@ -1 +1 @@ -Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d +Subproject commit 01270edbb4b6b4fb004e22fc529bf787c7f5be81