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

This commit is contained in:
Kelvin 2024-04-16 21:15:28 +02:00
parent b370af9d91
commit 66c7741c38
13 changed files with 103 additions and 17 deletions

View file

@ -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<String>, filterValues: HashMap<String, List<String>>): SlideUpMenuFilters {
val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues);
fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>, isChannelSearch: Boolean = false): SlideUpMenuFilters {
val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues, isChannelSearch);
overlay.show();
return overlay;
}

View file

@ -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,

View file

@ -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!!;

View file

@ -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 {

View file

@ -271,7 +271,7 @@ class ChannelFragment : MainFragment() {
_taskLoadPolycentricProfile.cancel();
_selectedTabIndex = -1;
if (!isBack) {
if (!isBack || _url == null) {
_imageBanner.setImageDrawable(null);
if (parameter is String) {

View file

@ -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<BrowserFragment>(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<BrowserFragment>(navUrl);
}
}

View file

@ -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) {

View file

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

View file

@ -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<BrowserFragment>(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 ->

View file

@ -144,10 +144,15 @@ class StateDownloads {
fun getDownloadedVideos(): List<VideoLocal> {
return _downloaded.getItems();
}
fun getDownloadedVideosPlaylist(str: String): List<VideoLocal> {
val videos = _downloaded.findItems { it.groupID == str };
return videos;
}
fun getDownloadPlaylists(): List<PlaylistDownloadDescriptor> {
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;
}
}

View file

@ -529,12 +529,23 @@ class StatePlatform {
}
fun getCommonSearchCapabilities(clientIds: List<String>): ResultCapabilities? {
return getCommonSearchCapabilitiesType(clientIds){
it.getSearchCapabilities()
};
}
fun getCommonSearchChannelContentsCapabilities(clientIds: List<String>): ResultCapabilities? {
return getCommonSearchCapabilitiesType(clientIds){
it.getSearchChannelContentsCapabilities()
};
}
fun getCommonSearchCapabilitiesType(clientIds: List<String>, 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<String>();
var sorts = cap.sorts.toMutableList();
@ -544,7 +555,7 @@ class StatePlatform {
val filtersToRemove = arrayListOf<Int>();
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])) {

View file

@ -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 {

View file

@ -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<String>, filterValues: HashMap<String, List<String>>) {
constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>, 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<String>();