From 51ac604e31eceab2015274aef2a8132301714129 Mon Sep 17 00:00:00 2001 From: Koen J Date: Mon, 21 Jul 2025 14:41:18 +0200 Subject: [PATCH] Various crash fixes. --- .../futo/platformplayer/UISlideOverlays.kt | 373 +++++++++++------- .../others/PlatformLinkMovementMethod.kt | 8 +- .../views/behavior/NonScrollingTextView.kt | 12 +- 3 files changed, 238 insertions(+), 155 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 83387081..409adbf5 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -129,115 +129,163 @@ class UISlideOverlays { val originalVideo = subscription.doFetchVideos; val originalPosts = subscription.doFetchPosts; - val menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, listOf()); + val menu = SlideUpMenuOverlay( + container.context, + container, + "Subscription Settings", + null, + true, + listOf() + ); - StateApp.instance.scopeOrNull?.launch(Dispatchers.IO){ - val plugin = StatePlatform.instance.getChannelClient(subscription.channel.url); - val capabilities = plugin.getChannelCapabilities(); + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + val plugin = StatePlatform.instance.getChannelClient(subscription.channel.url); + val capabilities = plugin.getChannelCapabilities(); - withContext(Dispatchers.Main) { - items.addAll(listOf( - SlideUpMenuItem( - container.context, - R.drawable.ic_notifications, - "Notifications", - "", - tag = "notifications", - call = { - subscription.doNotifications = menu?.selectOption(null, "notifications", true, true) ?: subscription.doNotifications; - }, - invokeParent = false - ), - if(StateSubscriptionGroups.instance.getSubscriptionGroups().isNotEmpty()) - SlideUpMenuGroup(container.context, "Subscription Groups", - "You can select which groups this subscription is part of.", - -1, listOf()) else null, - if(StateSubscriptionGroups.instance.getSubscriptionGroups().isNotEmpty()) - SlideUpMenuRecycler(container.context, "as") { - val groups = ArrayList(StateSubscriptionGroups.instance.getSubscriptionGroups() - .map { SubscriptionGroup.Selectable(it, it.urls.contains(subscription.channel.url)) } - .sortedBy { !it.selected }); - var adapter: AnyAdapterView? = null; - adapter = it.asAny(groups, RecyclerView.HORIZONTAL) { - it.onClick.subscribe { - if(it is SubscriptionGroup.Selectable) { - val actualGroup = StateSubscriptionGroups.instance.getSubscriptionGroup(it.id) - ?: return@subscribe; - groups.clear(); - if(it.selected) - actualGroup.urls.remove(subscription.channel.url); - else - actualGroup.urls.add(subscription.channel.url); + withContext(Dispatchers.Main) { + items.addAll( + listOf( + SlideUpMenuItem( + container.context, + R.drawable.ic_notifications, + "Notifications", + "", + tag = "notifications", + call = { + subscription.doNotifications = + menu?.selectOption(null, "notifications", true, true) + ?: subscription.doNotifications; + }, + invokeParent = false + ), + if (StateSubscriptionGroups.instance.getSubscriptionGroups() + .isNotEmpty() + ) + SlideUpMenuGroup( + container.context, "Subscription Groups", + "You can select which groups this subscription is part of.", + -1, listOf() + ) else null, + if (StateSubscriptionGroups.instance.getSubscriptionGroups() + .isNotEmpty() + ) + SlideUpMenuRecycler(container.context, "as") { + val groups = + ArrayList( + StateSubscriptionGroups.instance.getSubscriptionGroups() + .map { + SubscriptionGroup.Selectable( + it, + it.urls.contains(subscription.channel.url) + ) + } + .sortedBy { !it.selected }); + var adapter: AnyAdapterView? = + null; + adapter = it.asAny(groups, RecyclerView.HORIZONTAL) { + it.onClick.subscribe { + if (it is SubscriptionGroup.Selectable) { + val actualGroup = + StateSubscriptionGroups.instance.getSubscriptionGroup( + it.id + ) + ?: return@subscribe; + groups.clear(); + if (it.selected) + actualGroup.urls.remove(subscription.channel.url); + else + actualGroup.urls.add(subscription.channel.url); - StateSubscriptionGroups.instance.updateSubscriptionGroup(actualGroup); - groups.addAll(StateSubscriptionGroups.instance.getSubscriptionGroups() - .map { SubscriptionGroup.Selectable(it, it.urls.contains(subscription.channel.url)) } - .sortedBy { !it.selected }); - adapter?.notifyContentChanged(); - } - } - }; - return@SlideUpMenuRecycler adapter; - } else null, - SlideUpMenuGroup(container.context, "Fetch Settings", - "Depending on the platform you might not need to enable a type for it to be available.", - -1, listOf()), - if(capabilities.hasType(ResultCapabilities.TYPE_LIVE)) SlideUpMenuItem( - container.context, - R.drawable.ic_live_tv, - "Livestreams", - "Check for livestreams", - tag = "fetchLive", - call = { - subscription.doFetchLive = menu?.selectOption(null, "fetchLive", true, true) ?: subscription.doFetchLive; - }, - invokeParent = false - ) else null, - if(capabilities.hasType(ResultCapabilities.TYPE_STREAMS)) SlideUpMenuItem( - container.context, - R.drawable.ic_play, - "Streams", - "Check for streams", - tag = "fetchStreams", - call = { - subscription.doFetchStreams = menu?.selectOption(null, "fetchStreams", true, true) ?: subscription.doFetchStreams; - }, - invokeParent = false - ) else null, - if(capabilities.hasType(ResultCapabilities.TYPE_VIDEOS)) - SlideUpMenuItem( - container.context, - R.drawable.ic_play, - "Videos", - "Check for videos", - tag = "fetchVideos", - call = { - subscription.doFetchVideos = menu?.selectOption(null, "fetchVideos", true, true) ?: subscription.doFetchVideos; - }, - invokeParent = false - ) else if(capabilities.hasType(ResultCapabilities.TYPE_MIXED) || capabilities.types.isEmpty()) - SlideUpMenuItem( - container.context, - R.drawable.ic_play, - "Content", - "Check for content", - tag = "fetchVideos", - call = { - subscription.doFetchVideos = menu?.selectOption(null, "fetchVideos", true, true) ?: subscription.doFetchVideos; - }, - invokeParent = false - ) else null, - if(capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem( - container.context, - R.drawable.ic_chat, - "Posts", - "Check for posts", - tag = "fetchPosts", - call = { - subscription.doFetchPosts = menu?.selectOption(null, "fetchPosts", true, true) ?: subscription.doFetchPosts; - }, - invokeParent = false - ) else null/*,, + StateSubscriptionGroups.instance.updateSubscriptionGroup( + actualGroup + ); + groups.addAll( + StateSubscriptionGroups.instance.getSubscriptionGroups() + .map { + SubscriptionGroup.Selectable( + it, + it.urls.contains(subscription.channel.url) + ) + } + .sortedBy { !it.selected }); + adapter?.notifyContentChanged(); + } + } + }; + return@SlideUpMenuRecycler adapter; + } else null, + SlideUpMenuGroup( + container.context, "Fetch Settings", + "Depending on the platform you might not need to enable a type for it to be available.", + -1, listOf() + ), + if (capabilities.hasType(ResultCapabilities.TYPE_LIVE)) SlideUpMenuItem( + container.context, + R.drawable.ic_live_tv, + "Livestreams", + "Check for livestreams", + tag = "fetchLive", + call = { + subscription.doFetchLive = + menu?.selectOption(null, "fetchLive", true, true) + ?: subscription.doFetchLive; + }, + invokeParent = false + ) else null, + if (capabilities.hasType(ResultCapabilities.TYPE_STREAMS)) SlideUpMenuItem( + container.context, + R.drawable.ic_play, + "Streams", + "Check for streams", + tag = "fetchStreams", + call = { + subscription.doFetchStreams = + menu?.selectOption(null, "fetchStreams", true, true) + ?: subscription.doFetchStreams; + }, + invokeParent = false + ) else null, + if (capabilities.hasType(ResultCapabilities.TYPE_VIDEOS)) + SlideUpMenuItem( + container.context, + R.drawable.ic_play, + "Videos", + "Check for videos", + tag = "fetchVideos", + call = { + subscription.doFetchVideos = + menu?.selectOption(null, "fetchVideos", true, true) + ?: subscription.doFetchVideos; + }, + invokeParent = false + ) else if (capabilities.hasType(ResultCapabilities.TYPE_MIXED) || capabilities.types.isEmpty()) + SlideUpMenuItem( + container.context, + R.drawable.ic_play, + "Content", + "Check for content", + tag = "fetchVideos", + call = { + subscription.doFetchVideos = + menu?.selectOption(null, "fetchVideos", true, true) + ?: subscription.doFetchVideos; + }, + invokeParent = false + ) else null, + if (capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem( + container.context, + R.drawable.ic_chat, + "Posts", + "Check for posts", + tag = "fetchPosts", + call = { + subscription.doFetchPosts = + menu?.selectOption(null, "fetchPosts", true, true) + ?: subscription.doFetchPosts; + }, + invokeParent = false + ) else null/*,, SlideUpMenuGroup(container.context, "Actions", "Various things you can do with this subscription", @@ -245,61 +293,82 @@ class UISlideOverlays { SlideUpMenuItem(container.context, R.drawable.ic_list, "Add to Group", "", "btnAddToGroup", { showCreateSubscriptionGroup(container, subscription.channel); }, false)*/ - ).filterNotNull()); + ).filterNotNull() + ); - menu.setItems(items); + menu.setItems(items); - if(subscription.doNotifications) - menu.selectOption(null, "notifications", true, true); - if(subscription.doFetchLive) - menu.selectOption(null, "fetchLive", true, true); - if(subscription.doFetchStreams) - menu.selectOption(null, "fetchStreams", true, true); - if(subscription.doFetchVideos) - menu.selectOption(null, "fetchVideos", true, true); - if(subscription.doFetchPosts) - menu.selectOption(null, "fetchPosts", true, true); + if (subscription.doNotifications) + menu.selectOption(null, "notifications", true, true); + if (subscription.doFetchLive) + menu.selectOption(null, "fetchLive", true, true); + if (subscription.doFetchStreams) + menu.selectOption(null, "fetchStreams", true, true); + if (subscription.doFetchVideos) + menu.selectOption(null, "fetchVideos", true, true); + if (subscription.doFetchPosts) + menu.selectOption(null, "fetchPosts", true, true); - menu.onOK.subscribe { - subscription.save(); - menu.hide(true); + menu.onOK.subscribe { + subscription.save(); + menu.hide(true); - 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 (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"); + 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 { - subscription.doNotifications = originalNotif; - subscription.doFetchLive = originalLive; - subscription.doFetchStreams = originalStream; - subscription.doFetchVideos = originalVideo; - subscription.doFetchPosts = originalPosts; - }; + }; + menu.onCancel.subscribe { + subscription.doNotifications = originalNotif; + subscription.doFetchLive = originalLive; + subscription.doFetchStreams = originalStream; + subscription.doFetchVideos = originalVideo; + subscription.doFetchPosts = originalPosts; + }; - menu.setOk("Save"); + menu.setOk("Save"); - menu.show(); + menu.show(); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show subscription overlay.", e) } } diff --git a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt index 76652236..b29ebd87 100644 --- a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt +++ b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt @@ -13,6 +13,8 @@ import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.timestampRegex +import com.futo.platformplayer.views.behavior.NonScrollingTextView +import com.futo.platformplayer.views.behavior.NonScrollingTextView.Companion import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -91,7 +93,11 @@ class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMe } withContext(Dispatchers.Main) { - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + try { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } catch (e: Throwable) { + Logger.i(TAG, "Failed to start activity.", e) + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt index 6e3a8860..2d1aa511 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt @@ -108,12 +108,20 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { } withContext(Dispatchers.Main) { - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + try { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } catch (e: Throwable) { + Logger.i(TAG, "Failed to start activity.", e) + } } } } else { StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + try { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } catch (e: Throwable) { + Logger.i(TAG, "Failed to start activity.", e) + } } } }