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();