From 66c7741c38a439f28541438a1ce9125b89344411 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 16 Apr 2024 21:15:28 +0200 Subject: [PATCH] Deleting playlist video deletes local files, Post links are now clickable, going back to channel page from post now shows channel page correctly, search capabilities correct for channel content search, Fix loader not disappearing in certain cases on post details --- .../futo/platformplayer/UISlideOverlays.kt | 4 ++-- .../api/media/PlatformClientCapabilities.kt | 1 + .../api/media/platforms/js/JSClient.kt | 4 ++++ .../engine/packages/PackageUtilities.kt | 14 ++++++++++++ .../mainactivity/main/ChannelFragment.kt | 2 +- .../mainactivity/main/CommentsFragment.kt | 7 ++++-- .../main/ContentSearchResultsFragment.kt | 8 +++++-- .../mainactivity/main/PostDetailFragment.kt | 3 +++ .../mainactivity/main/VideoDetailView.kt | 10 ++++----- .../platformplayer/states/StateDownloads.kt | 22 ++++++++++++++++++- .../platformplayer/states/StatePlatform.kt | 15 +++++++++++-- .../platformplayer/states/StatePlaylists.kt | 19 ++++++++++++++++ .../overlays/slideup/SlideUpMenuFilters.kt | 11 ++++++++-- 13 files changed, 103 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 6723d06f..386992ea 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -788,8 +788,8 @@ class UISlideOverlays { return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.add_to), null, true, items).apply { show() }; } - fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>): SlideUpMenuFilters { - val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues); + fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>, isChannelSearch: Boolean = false): SlideUpMenuFilters { + val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues, isChannelSearch); overlay.show(); return overlay; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt index 84edbf94..234ce74f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt @@ -13,6 +13,7 @@ data class PlatformClientCapabilities( val hasGetChannelUrlByClaim: Boolean = false, val hasGetChannelTemplateByClaimMap: Boolean = false, val hasGetSearchCapabilities: Boolean = false, + val hasGetSearchChannelContentsCapabilities: Boolean = false, val hasGetChannelCapabilities: Boolean = false, val hasGetLiveEvents: Boolean = false, val hasGetLiveChatWindow: Boolean = false, diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index d33ab197..258995d9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -216,6 +216,7 @@ open class JSClient : IPlatformClient { hasGetChannelTemplateByClaimMap = plugin.executeBoolean("!!source.getChannelTemplateByClaimMap") ?: false, hasGetSearchCapabilities = plugin.executeBoolean("!!source.getSearchCapabilities") ?: false, hasGetChannelCapabilities = plugin.executeBoolean("!!source.getChannelCapabilities") ?: false, + hasGetSearchChannelContentsCapabilities = plugin.executeBoolean("!!source.getSearchChannelContentsCapabilities") ?: false, hasGetLiveEvents = plugin.executeBoolean("!!source.getLiveEvents") ?: false, hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false, hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false, @@ -308,6 +309,9 @@ open class JSClient : IPlatformClient { @JSDocs(4, "source.getSearchChannelContentsCapabilities()", "Gets capabilities this plugin has for search videos") override fun getSearchChannelContentsCapabilities(): ResultCapabilities { + if(!capabilities.hasGetSearchChannelContentsCapabilities) + return ResultCapabilities(listOf(ResultCapabilities.TYPE_MIXED)); + ensureEnabled(); if (_searchChannelContentsCapabilities != null) return _searchChannelContentsCapabilities!!; diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt index 507e5f28..58653dc0 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt @@ -34,6 +34,20 @@ class PackageUtilities : V8Package { fun md5(arr: ByteArray): ByteArray { return MessageDigest.getInstance("MD5").digest(arr); } + @V8Function + fun md5String(str: String): String { + return md5(str.toByteArray(Charsets.UTF_8)).fold("") { str, it -> str + "%02x".format(it) }; + } + + + @V8Function + fun sha256(arr: ByteArray): ByteArray { + return MessageDigest.getInstance("SHA-256").digest(arr); + } + @V8Function + fun sha256String(str: String): String { + return sha256(str.toByteArray(Charsets.UTF_8)).fold("") { str, it -> str + "%02x".format(it) }; + } @V8Function fun randomUUID(): String { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index d041803b..602620ad 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -271,7 +271,7 @@ class ChannelFragment : MainFragment() { _taskLoadPolycentricProfile.cancel(); _selectedTabIndex = -1; - if (!isBack) { + if (!isBack || _url == null) { _imageBanner.setImageDrawable(null); if (parameter is String) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt index 13a4e3cb..cf74c79c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt @@ -1,6 +1,7 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.content.Intent +import android.net.Uri import android.os.Bundle import android.provider.Browser import android.view.LayoutInflater @@ -220,8 +221,10 @@ class CommentsFragment : MainFragment() { Logger.i(TAG, "onAuthorClick: " + c.author.id.value); if(c.author.id.value?.startsWith("polycentric://") ?: false) { - val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length); - _fragment.navigate(navUrl); + //val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length); + val navUrl = "https://polycentric.io/user/" + c.author.id.value?.substring("polycentric://".length); + _fragment.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(navUrl))) + //_fragment.navigate(navUrl); } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index badbf751..82831686 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -129,7 +129,7 @@ class ContentSearchResultsFragment : MainFragment() { onFilterClick.subscribe(this) { _overlayContainer.let { val filterValuesCopy = HashMap(_filterValues); - val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy); + val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy, _channelUrl != null); filtersOverlay.onOK.subscribe { enabledClientIds, changed -> if (changed) { setFilterValues(filtersOverlay.commonCapabilities, filterValuesCopy); @@ -170,7 +170,11 @@ class ContentSearchResultsFragment : MainFragment() { fragment.lifecycleScope.launch(Dispatchers.IO) { try { - val commonCapabilities = StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); + val commonCapabilities = + if(_channelUrl == null) + StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); + else + StatePlatform.instance.getCommonSearchChannelContentsCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); val sorts = commonCapabilities?.sorts ?: listOf(); if (sorts.size > 1) { withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt index e5bdfebc..3cf96f60 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt @@ -35,6 +35,7 @@ import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePolycentric @@ -211,6 +212,8 @@ class PostDetailFragment : MainFragment { _repliesOverlay = findViewById(R.id.replies_overlay); + _textContent.setPlatformPlayerLinkMovementMethod(context); + _buttonSubscribe.onSubscribed.subscribe { //TODO: add overlay to layout //UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 6b63a39f..5413c0f6 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -656,11 +656,11 @@ class VideoDetailView : ConstraintLayout { Logger.i(TAG, "onAuthorClick: " + c.author.id.value); if(c.author.id.value?.startsWith("polycentric://") ?: false) { - val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length); - //fragment.navigate(navUrl); - //fragment.minimizeVideoDetail(); - _container_content_browser.goto(navUrl); - switchContentView(_container_content_browser); + //val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length); + val navUrl = "https://polycentric.io/user/" + c.author.id.value?.substring("polycentric://".length); + fragment.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(navUrl))) + //_container_content_browser.goto(navUrl); + //switchContentView(_container_content_browser); } }; _commentsList.onRepliesClick.subscribe { c -> diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt index 5164a0b1..cf5d762f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt @@ -144,10 +144,15 @@ class StateDownloads { fun getDownloadedVideos(): List { return _downloaded.getItems(); } + fun getDownloadedVideosPlaylist(str: String): List { + val videos = _downloaded.findItems { it.groupID == str }; + return videos; + } fun getDownloadPlaylists(): List { return _downloadPlaylists.getItems(); } + fun isPlaylistCached(id: String): Boolean { return getDownloadPlaylists().any{it.id == id}; } @@ -188,6 +193,21 @@ class StateDownloads { DownloadService.getOrCreateService(it); } } + + fun checkForOutdatedPlaylistVideos(playlistId: String) { + val playlistVideos = if(playlistId == VideoDownload.GROUP_WATCHLATER) + (if(getWatchLaterDescriptor() != null) StatePlaylists.instance.getWatchLater() else listOf()) + else + getCachedPlaylist(playlistId)?.playlist?.videos ?: return; + val playlistVideosDownloaded = getDownloadedVideosPlaylist(playlistId); + val urls = playlistVideos.map { it.url }.toHashSet(); + for(item in playlistVideosDownloaded) { + if(!urls.contains(item.url)) { + Logger.i(TAG, "Playlist [${playlistId}] deleting removed video [${item.name}]"); + deleteCachedVideo(item.id); + } + } + } fun checkForOutdatedPlaylists(): Boolean { var hasChanged = false; val playlistsDownloaded = getCachedPlaylists(); @@ -230,7 +250,7 @@ class StateDownloads { else { Logger.i(TAG, "New watchlater video ${item.name}"); download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate) - .withGroup(VideoDownload.GROUP_PLAYLIST, VideoDownload.GROUP_WATCHLATER), false); + .withGroup(VideoDownload.GROUP_WATCHLATER, VideoDownload.GROUP_WATCHLATER), false); hasNew = true; } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index c9eb0fde..5c5709fd 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -529,12 +529,23 @@ class StatePlatform { } fun getCommonSearchCapabilities(clientIds: List): ResultCapabilities? { + return getCommonSearchCapabilitiesType(clientIds){ + it.getSearchCapabilities() + }; + } + fun getCommonSearchChannelContentsCapabilities(clientIds: List): ResultCapabilities? { + return getCommonSearchCapabilitiesType(clientIds){ + it.getSearchChannelContentsCapabilities() + }; + } + + fun getCommonSearchCapabilitiesType(clientIds: List, capabilitiesGetter: (client: IPlatformClient)-> ResultCapabilities): ResultCapabilities? { try { Logger.i(TAG, "Platform - getCommonSearchCapabilities"); val clients = getEnabledClients().filter { clientIds.contains(it.id) }; val c = clients.firstOrNull() ?: return null; - val cap = c.getSearchCapabilities(); + val cap = capabilitiesGetter(c)//c.getSearchCapabilities(); //var types = arrayListOf(); var sorts = cap.sorts.toMutableList(); @@ -544,7 +555,7 @@ class StatePlatform { val filtersToRemove = arrayListOf(); for (i in 1 until clients.size) { - val clientSearchCapabilities = clients[i].getSearchCapabilities(); + val clientSearchCapabilities = capabilitiesGetter(clients[i]);//.getSearchCapabilities(); for (j in 0 until sorts.size) { if (!clientSearchCapabilities.sorts.contains(sorts[j])) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt index 84b6eb51..41de7c3c 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -11,6 +11,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.downloads.VideoDownload import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.exceptions.ReconstructionException import com.futo.platformplayer.logging.Logger @@ -66,6 +67,10 @@ class StatePlaylists { _watchlistOrderStore.save(); } onWatchLaterChanged.emit(); + + if(StateDownloads.instance.getWatchLaterDescriptor() != null) { + StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER); + } } fun removeFromWatchLater(video: SerializedPlatformVideo) { synchronized(_watchlistStore) { @@ -74,6 +79,10 @@ class StatePlaylists { _watchlistOrderStore.save(); } onWatchLaterChanged.emit(); + + if(StateDownloads.instance.getWatchLaterDescriptor() != null) { + StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER); + } } fun addToWatchLater(video: SerializedPlatformVideo) { synchronized(_watchlistStore) { @@ -82,6 +91,8 @@ class StatePlaylists { _watchlistOrderStore.save(); } onWatchLaterChanged.emit(); + + StateDownloads.instance.checkForOutdatedPlaylists(); } fun getLastPlayedPlaylist() : Playlist? { @@ -131,6 +142,11 @@ class StatePlaylists { fun createOrUpdatePlaylist(playlist: Playlist) { playlist.dateUpdate = OffsetDateTime.now(); playlistStore.saveAsync(playlist, true); + if(playlist.id.isNotEmpty()) { + if (StateDownloads.instance.isPlaylistCached(playlist.id)) { + StateDownloads.instance.checkForOutdatedPlaylistVideos(playlist.id); + } + } } fun addToPlaylist(id: String, video: IPlatformVideo) { synchronized(playlistStore) { @@ -143,6 +159,9 @@ class StatePlaylists { fun removePlaylist(playlist: Playlist) { playlistStore.delete(playlist); + if(StateDownloads.instance.isPlaylistCached(playlist.id)) { + StateDownloads.instance.deleteCachedPlaylist(playlist.id); + } } fun createPlaylistShareUri(context: Context, playlist: Playlist): Uri { diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt index 13f382ae..75b50a26 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt @@ -28,13 +28,17 @@ class SlideUpMenuFilters { private var _changed: Boolean = false; private val _lifecycleScope: CoroutineScope; + private var _isChannelSearch = false; + var commonCapabilities: ResultCapabilities? = null; - constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>) { + + constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>, isChannelSearch: Boolean = false) { _lifecycleScope = lifecycleScope; _container = container; _enabledClientsIds = enabledClientsIds; _filterValues = filterValues; + _isChannelSearch = isChannelSearch; _slideUpMenuOverlay = SlideUpMenuOverlay(_container.context, _container, container.context.getString(R.string.filters), container.context.getString(R.string.done), true, listOf()); _slideUpMenuOverlay.onOK.subscribe { onOK.emit(_enabledClientsIds, _changed); @@ -47,7 +51,10 @@ class SlideUpMenuFilters { private fun updateCommonCapabilities() { _lifecycleScope.launch(Dispatchers.IO) { try { - val caps = StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds); + val caps = if(!_isChannelSearch) + StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds); + else + StatePlatform.instance.getCommonSearchChannelContentsCapabilities(_enabledClientsIds); synchronized(_filterValues) { if (caps != null) { val keysToRemove = arrayListOf();