Progress bars subscription groups, Various notification permission requests, improved notification experience, Settings activity open to specific settings, refs

This commit is contained in:
Kelvin 2023-12-19 23:08:38 +01:00
parent f42b14e95a
commit b7c4047f1d
8 changed files with 134 additions and 13 deletions

View File

@ -277,7 +277,7 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 9) @FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 9)
var fetchOnTabOpen: Boolean = true; 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) @DropdownFieldOptionsId(R.array.background_interval)
var subscriptionsBackgroundUpdateInterval: Int = 0; var subscriptionsBackgroundUpdateInterval: Int = 0;

View File

@ -1,9 +1,14 @@
package com.futo.platformplayer package com.futo.platformplayer
import android.app.Activity
import android.app.NotificationManager
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
@ -131,8 +136,29 @@ class UISlideOverlays {
subscription.save(); subscription.save();
menu.hide(true); menu.hide(true);
if(subscription.doNotifications && !originalNotif && Settings.instance.subscriptions.subscriptionsBackgroundUpdateInterval == 0) { if(subscription.doNotifications && !originalNotif) {
UIDialogs.toast(container.context, "Enable 'Background Update' in settings for notifications to work"); 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 { menu.onCancel.subscribe {

View File

@ -1,10 +1,12 @@
package com.futo.platformplayer.activities package com.futo.platformplayer.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -17,6 +19,8 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout 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.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat 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. //TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers.
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult)->Unit>(); private var resultLauncherMap = mutableMapOf<Int, (ActivityResult)->Unit>();

View File

@ -1,8 +1,10 @@
package com.futo.platformplayer.activities package com.futo.platformplayer.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
@ -12,6 +14,8 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
@ -33,6 +37,14 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
lateinit var overlay: FrameLayout; 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?) { override fun attachBaseContext(newBase: Context?) {
Logger.i("SettingsActivity", "SettingsActivity.attachBaseContext") Logger.i("SettingsActivity", "SettingsActivity.attachBaseContext")
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase)) 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"); Logger.i("SettingsActivity", "App language change detected, propogating to shared preferences");
StateApp.instance.setLocaleSetting(this, Settings.instance.language.getAppLanguageLocaleString()); 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 { _buttonBack.setOnClickListener {
finish(); finish();
@ -72,7 +111,10 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
reloadSettings(); reloadSettings();
} }
var isFirstLoad = true;
fun reloadSettings() { fun reloadSettings() {
val firstLoad = isFirstLoad;
isFirstLoad = false;
_form.setSearchVisible(false); _form.setSearchVisible(false);
_loaderView.start(); _loaderView.start();
_form.fromObject(lifecycleScope, Settings.instance) { _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)); 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); resultLauncher.launch(intent);
} }
companion object { companion object {
//TODO: Temporary for solving Settings issues //TODO: Temporary for solving Settings issues
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")

View File

@ -104,14 +104,17 @@ class SubscriptionsFeedFragment : MainFragment() {
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) { constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
Logger.i(TAG, "SubscriptionsFeedFragment constructor()"); Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total -> StateSubscriptions.instance.onFeedProgress.subscribe(this) { id, progress, total ->
fragment.lifecycleScope.launch(Dispatchers.Main) { if(_subGroup?.id == id)
try { fragment.lifecycleScope.launch(Dispatchers.Main) {
setProgress(progress, total); try {
} catch (e: Throwable) { setProgress(progress, total);
Logger.e(TAG, "Failed to set progress", e); } 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 -> StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { _, added ->
@ -264,7 +267,10 @@ class SubscriptionsFeedFragment : MainFragment() {
UISlideOverlays.showCreateSubscriptionGroup(_overlayContainer); UISlideOverlays.showCreateSubscriptionGroup(_overlayContainer);
else { else {
_subGroup = g; _subGroup = g;
loadCache(); //TODO: Proper subset update setProgress(0, 0);
if(Settings.instance.subscriptions.fetchOnTabOpen)
loadResults(false);
else loadCache();
} }
}; };
_subscriptionBar?.onHoldGroup?.subscribe { g -> _subscriptionBar?.onHoldGroup?.subscribe { g ->

View File

@ -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.*
import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.functional.CentralizedFeed import com.futo.platformplayer.functional.CentralizedFeed
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.Subscription
@ -50,7 +51,7 @@ class StateSubscriptions {
val global: CentralizedFeed = CentralizedFeed(); val global: CentralizedFeed = CentralizedFeed();
val feeds: HashMap<String, CentralizedFeed> = hashMapOf(); val feeds: HashMap<String, CentralizedFeed> = hashMapOf();
val onFeedProgress = Event3<String, Int, Int>();
val onSubscriptionsChanged = Event2<List<Subscription>, Boolean>(); val onSubscriptionsChanged = Event2<List<Subscription>, Boolean>();
@ -70,6 +71,9 @@ class StateSubscriptions {
var f = feeds[id]; var f = feeds[id];
if(f == null && createIfNew) { if(f == null && createIfNew) {
f = CentralizedFeed(); f = CentralizedFeed();
f.onUpdateProgress.subscribe { progress, total ->
onFeedProgress.emit(id, progress, total)
};
feeds[id] = f; feeds[id] = f;
} }
return@synchronized f; return@synchronized f;

View File

@ -71,6 +71,10 @@ class FieldForm : LinearLayout {
} }
} }
fun setSearchQuery(query: String) {
_editSearch.setText(query);
updateSettingsVisibility();
}
fun setSearchVisible(visible: Boolean) { fun setSearchVisible(visible: Boolean) {
_containerSearch.visibility = if(visible) View.VISIBLE else View.GONE; _containerSearch.visibility = if(visible) View.VISIBLE else View.GONE;
_editSearch.setText(""); _editSearch.setText("");

@ -1 +1 @@
Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d Subproject commit 01270edbb4b6b4fb004e22fc529bf787c7f5be81