diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt index 8e4399ec..4ddf37ad 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt @@ -226,6 +226,25 @@ fun Long.toHumanTime(isMs: Boolean): String { else return "${prefix}${minsStr}:${secsStr}" } +fun Long.toHumanDuration(isMs: Boolean): String { + var scaler = 1; + if(isMs) + scaler = 1000; + val v = Math.abs(this); + val hours = Math.max(v/(secondsInHour*scaler), 0); + val mins = Math.max((v % (secondsInHour*scaler)) / (secondsInMinute * scaler), 0); + val minsStr = mins.toString(); + val seconds = Math.max(((v % (secondsInHour*scaler)) % (secondsInMinute * scaler))/scaler, 0); + val secsStr = seconds.toString().padStart(2, '0'); + val prefix = if (this < 0) { "-" } else { "" }; + + return listOf( + if(hours > 0) "${hours}h" else null, + if(mins > 0) "${mins}m" else null , + if(seconds > 0) "${seconds}s" else null + ).filterNotNull().joinToString(" "); +} + //TODO: Determine if below stuff should have its own proper class, seems a bit too complex for a utility method fun String.fixHtmlWhitespace(): Spanned { diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 270158fd..102e7d09 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -254,6 +254,9 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.progress_bar, FieldForm.TOGGLE, R.string.progress_bar_description, 6) var progressBar: Boolean = true; + @FormField(R.string.hide_hidden_from_search, FieldForm.TOGGLE, R.string.hide_hidden_from_search_description, 7) + var hidefromSearch: Boolean = false; + fun getSearchFeedStyle(): FeedStyle { if(searchFeedStyle == 0) diff --git a/app/src/main/java/com/futo/platformplayer/UIDialogs.kt b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt index 9d98a8f1..e84002da 100644 --- a/app/src/main/java/com/futo/platformplayer/UIDialogs.kt +++ b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt @@ -368,8 +368,8 @@ class UIDialogs { } } - fun showChangelogDialog(context: Context, lastVersion: Int) { - val dialog = ChangelogDialog(context); + fun showChangelogDialog(context: Context, lastVersion: Int, changelogs: Map? = null) { + val dialog = ChangelogDialog(context, changelogs); registerDialogOpened(dialog); dialog.setOnDismissListener { registerDialogClosed(dialog) }; dialog.show(); diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 2a930734..daf50455 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -897,6 +897,7 @@ class UISlideOverlays { call = { StatePlaylists.instance.addToPlaylist(lastUpdated.id, video); StateDownloads.instance.checkForOutdatedPlaylists(); + UIDialogs.appToast("Added to playlist [${lastUpdated?.name}]", false); })) ); } @@ -993,6 +994,7 @@ class UISlideOverlays { call = { StatePlaylists.instance.addToPlaylist(playlist.id, video); StateDownloads.instance.checkForOutdatedPlaylists(); + UIDialogs.appToast("Added to playlist [${playlist.name}]", false); })); } @@ -1020,6 +1022,7 @@ class UISlideOverlays { call = { StatePlaylists.instance.addToPlaylist(lastUpdated.id, video); StateDownloads.instance.checkForOutdatedPlaylists(); + UIDialogs.appToast("Added to playlist [${lastUpdated?.name}]", false); })) ); } @@ -1040,7 +1043,9 @@ class UISlideOverlays { StatePlayer.TYPE_WATCHLATER, "${watchLater.size} " + container.context.getString(R.string.videos), tag = "watch later", - call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }), + call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); + UIDialogs.appToast("Added to watch later", false); + }), ) ); @@ -1069,6 +1074,7 @@ class UISlideOverlays { call = { StatePlaylists.instance.addToPlaylist(playlist.id, video); StateDownloads.instance.checkForOutdatedPlaylists(); + UIDialogs.appToast("Added to playlist [${playlist.name}]", false); })); } diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 371b4d1b..25febdb1 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -1281,7 +1281,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { if (toast.long) delay(5000); else - delay(3000); + delay(2500); } Logger.i(TAG, "Ending appToast loop"); lifecycleScope.launch(Dispatchers.Main) { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index 3af034aa..e4290830 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -52,6 +52,7 @@ class SourcePluginConfig( var allowAllHttpHeaderAccess: Boolean = false, var maxDownloadParallelism: Int = 0, var reduceFunctionsInLimitedVersion: Boolean = false, + var changelog: HashMap>? = null ) : IV8PluginConfig { val absoluteIconUrl: String? get() = resolveAbsoluteUrl(iconUrl, sourceUrl); @@ -129,7 +130,7 @@ class SourcePluginConfig( val currentlyInstalledPlugin = StatePlugins.instance.getPlugin(id); if (currentlyInstalledPlugin != null) { - if (currentlyInstalledPlugin.config.scriptPublicKey != scriptPublicKey) { + if (currentlyInstalledPlugin.config.scriptPublicKey != scriptPublicKey && !currentlyInstalledPlugin.config.scriptPublicKey.isNullOrEmpty()) { list.add(Pair( "Different Author", "This plugin was signed by a different author. Please ensure that this is correct and that the plugin was not provided by a malicious actor.")); @@ -178,6 +179,19 @@ class SourcePluginConfig( return _allowUrlsLower.any { it == host || (it.length > 0 && it[0] == '.' && host.matchesDomain(it)) }; } + fun getChangelogString(version: String): String?{ + if(changelog == null || !changelog!!.containsKey(version)) + return null; + val changelog = changelog!![version]!!; + if(changelog.size > 1) { + return "Changelog (${version})\n" + changelog.map { " - " + it.trim() }.joinToString("\n"); + } + else if(changelog.size == 1) { + return "Changelog (${version})\n" + changelog[0].trim(); + } + return null; + } + companion object { fun fromJson(json: String, sourceUrl: String? = null): SourcePluginConfig { val obj = Serializer.json.decodeFromString(json); diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt index 38f4f65a..f1a44302 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt @@ -1,37 +1,24 @@ package com.futo.platformplayer.dialogs import android.app.AlertDialog -import android.app.PendingIntent.* import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller import android.graphics.drawable.Animatable import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.view.LayoutInflater import android.view.View -import android.view.WindowManager import android.widget.Button import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.* -import com.futo.platformplayer.receivers.InstallReceiver import com.futo.platformplayer.api.http.ManagedHttpClient -import com.futo.platformplayer.api.media.models.PlatformAuthorLink -import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler -import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StateUpdate -import kotlinx.coroutines.* -import java.io.File -import java.io.InputStream -class ChangelogDialog(context: Context?) : AlertDialog(context) { +class ChangelogDialog(context: Context?, val changelogs: Map? = null) : AlertDialog(context) { companion object { private val TAG = "ChangelogDialog"; } @@ -48,7 +35,11 @@ class ChangelogDialog(context: Context?) : AlertDialog(context) { private var _maxVersion: Int = 0; private var _managedHttpClient = ManagedHttpClient(); - private val _taskDownloadChangelog = TaskHandler(StateApp.instance.scopeGetter, { version -> StateUpdate.instance.downloadChangelog(_managedHttpClient, version) }) + private val _taskDownloadChangelog = TaskHandler(StateApp.instance.scopeGetter, { version -> if(changelogs == null) + StateUpdate.instance.downloadChangelog(_managedHttpClient, version) + else + changelogs[version] + }) .success { setChangelog(it); } .exception { Logger.w(TAG, "Failed to load changelog.", it); @@ -97,7 +88,7 @@ class ChangelogDialog(context: Context?) : AlertDialog(context) { setVersion(version); val currentVersion = BuildConfig.VERSION_CODE; - _buttonUpdate.visibility = if (currentVersion == _maxVersion) View.GONE else View.VISIBLE; + _buttonUpdate.visibility = if (currentVersion == _maxVersion || changelogs != null) View.GONE else View.VISIBLE; } private fun setVersion(version: Int) { diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt index b7109a81..f9a64888 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt @@ -54,6 +54,7 @@ class PluginUpdateDialog : AlertDialog { private lateinit var _buttonInstall: LinearLayout; private lateinit var _textPlugin: TextView; + private lateinit var _textChangelog: TextView; private lateinit var _textProgres: TextView; private lateinit var _textError: TextView; private lateinit var _textResult: TextView; @@ -94,6 +95,7 @@ class PluginUpdateDialog : AlertDialog { _buttonInstall = findViewById(R.id.button_install); _textPlugin = findViewById(R.id.text_plugin); + _textChangelog = findViewById(R.id.text_changelog); _textProgres = findViewById(R.id.text_progress); _textError = findViewById(R.id.text_error); _textResult = findViewById(R.id.text_result); @@ -110,6 +112,27 @@ class PluginUpdateDialog : AlertDialog { _updateSpinner = findViewById(R.id.update_spinner); _iconPlugin = findViewById(R.id.icon_plugin); + try { + var changelogVersion = _newConfig.version.toString(); + if (_newConfig.changelog != null && _newConfig.changelog?.containsKey(changelogVersion) == true) { + _textChangelog.movementMethod = ScrollingMovementMethod(); + val changelog = _newConfig.changelog!![changelogVersion]!!; + if(changelog.size > 1) { + _textChangelog.text = "Changelog (${_newConfig.version})\n" + changelog.map { " - " + it.trim() }.joinToString("\n"); + } + else if(changelog.size == 1) { + _textChangelog.text = "Changelog (${_newConfig.version})\n" + changelog[0].trim(); + } + else + _textChangelog.visibility = View.GONE; + } else + _textChangelog.visibility = View.GONE; + } + catch(ex: Throwable) { + _textChangelog.visibility = View.GONE; + Logger.e(TAG, "Invalid changelog? ", ex); + } + _buttonCancel1.setOnClickListener { dismiss(); }; 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 076c0542..a078eb0c 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 @@ -17,6 +17,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment import com.futo.platformplayer.isHttpUrl import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.FeedStyle import kotlinx.coroutines.Dispatchers @@ -222,6 +223,12 @@ class ContentSearchResultsFragment : MainFragment() { setSortByOptions(null); } + override fun filterResults(results: List): List { + if(Settings.instance.search.hidefromSearch) + return super.filterResults(results.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) }); + return super.filterResults(results) + } + override fun reload() { loadResults(); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt index 59575bda..81669c73 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt @@ -146,7 +146,7 @@ class PlaylistFragment : MainFragment() { setName(it.name); //TODO: Implement support for pagination setVideos(it.videos, false); - setVideoCount(it.videos.size); + setMetadata(it.videos.size, it.videos.sumOf { it.duration }); setLoading(false); } .exception { @@ -174,7 +174,7 @@ class PlaylistFragment : MainFragment() { if (parameter != null) { setName(parameter.name) setVideos(parameter.videos, true) - setVideoCount(parameter.videos.size) + setMetadata(parameter.videos.size, parameter.videos.sumOf { it.duration }) setButtonDownloadVisible(true) setButtonEditVisible(true) @@ -187,7 +187,7 @@ class PlaylistFragment : MainFragment() { } else { setName(null) setVideos(null, false) - setVideoCount(-1) + setMetadata(-1, -1); setButtonDownloadVisible(false) setButtonEditVisible(false) } @@ -195,7 +195,7 @@ class PlaylistFragment : MainFragment() { _playlist = null _url = parameter.url - setVideoCount(parameter.videoCount) + setMetadata(parameter.videoCount, -1); setName(parameter.name) setVideos(null, false) setButtonDownloadVisible(false) @@ -208,7 +208,7 @@ class PlaylistFragment : MainFragment() { setName(null) setVideos(null, false) - setVideoCount(-1) + setMetadata(-1, -1); setButtonDownloadVisible(false) setButtonEditVisible(false) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index c5f3d430..1bdedfcd 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -237,7 +237,19 @@ class SourceDetailFragment : MainFragment() { BigButtonGroup(c, context.getString(R.string.update), BigButton(c, context.getString(R.string.check_for_updates), context.getString(R.string.checks_for_new_versions_of_the_source), R.drawable.ic_update) { checkForUpdatesSource(); - } + }, + if(config.changelog?.any() == true) + BigButton(c, context.getString(R.string.changelog), context.getString(R.string.changelog_plugin_description), R.drawable.ic_list) { + UIDialogs.showChangelogDialog(context, config.version, config.changelog!!.filterKeys { it.toIntOrNull() != null } + .mapKeys { it.key.toInt() } + .mapValues { config.getChangelogString(it.key.toString()) ?: "" }); + }.apply { + this.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { + setMargins(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt(), 0, 0); + }; + } + else + null ) ); @@ -544,7 +556,7 @@ class SourceDetailFragment : MainFragment() { Logger.i(TAG, "Downloaded source config ($sourceUrl):\n${configJson}"); val config = SourcePluginConfig.fromJson(configJson); - if (config.version <= c.version) { + if (config.version <= c.version && config.name != "Youtube") { Logger.i(TAG, "Plugin is up to date."); withContext(Dispatchers.Main) { UIDialogs.toast(context.getString(R.string.plugin_is_fully_up_to_date)); }; return@launch; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt index aa5d51d7..2e355fa4 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt @@ -20,6 +20,8 @@ import com.futo.platformplayer.downloads.VideoDownload import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.toHumanDuration +import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.lists.VideoListEditorView abstract class VideoListEditorView : LinearLayout { @@ -136,8 +138,14 @@ abstract class VideoListEditorView : LinearLayout { _textName.text = name ?: ""; } - protected fun setVideoCount(videoCount: Int = -1) { - _textMetadata.text = if (videoCount == -1) "" else "${videoCount} " + context.getString(R.string.videos); + protected fun setMetadata(videoCount: Int = -1, duration: Long = -1) { + val parts = mutableListOf() + if(videoCount >= 0) + parts.add("${videoCount} " + context.getString(R.string.videos)); + if(duration > 0) + parts.add("${duration.toHumanDuration(false)} "); + + _textMetadata.text = parts.joinToString(" • "); } protected fun setVideos(videos: List?, canEdit: Boolean) { diff --git a/app/src/main/java/com/futo/platformplayer/views/ToastView.kt b/app/src/main/java/com/futo/platformplayer/views/ToastView.kt index 2600dff2..f2066d13 100644 --- a/app/src/main/java/com/futo/platformplayer/views/ToastView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/ToastView.kt @@ -55,7 +55,7 @@ class ToastView : LinearLayout { translationY = 20.dp(context.resources).toFloat(); animate() .alpha(1f) - .setDuration(700) + .setDuration(300) .translationY(0f) .start(); } diff --git a/app/src/main/res/layout/dialog_plugin_update.xml b/app/src/main/res/layout/dialog_plugin_update.xml index 7c89e8bc..0e9a815e 100644 --- a/app/src/main/res/layout/dialog_plugin_update.xml +++ b/app/src/main/res/layout/dialog_plugin_update.xml @@ -93,6 +93,26 @@ android:layout_marginStart="30dp" android:layout_marginEnd="30dp" /> + + Home Progress Bar If a historical progress bar should be shown + Hide hidden from home in search + Hide videos and creators hidden from home also in search results Recommendations More Playlists @@ -195,6 +197,7 @@ Connected to Volume Changelog + Shows available changelogs for current and past versions Some example changelog. Previous Next @@ -398,8 +401,8 @@ If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility. Allow video under cutout Allow video to go underneath the screen cutout in full screen.\nMay require restart - Enable autoplay by default - Autoplay will be enabled by default whenever you watch a video + Autoplay next video by default + Autoplay next video will be enabled by default whenever you watch a video Allow full-screen portrait when watching horizontal videos Delete from WatchLater when watched After you leave a video that you mostly watched, it will be removed from watch later.