mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-19 19:14:51 +00:00
Plugin changelog support, Hide hidden from search setting, No author change warning if missing pubkey, toast on add to playlist, better autoplay description, Playlist total duration label
This commit is contained in:
parent
83b35183d0
commit
0b529ae94d
15 changed files with 139 additions and 33 deletions
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -368,8 +368,8 @@ class UIDialogs {
|
|||
}
|
||||
}
|
||||
|
||||
fun showChangelogDialog(context: Context, lastVersion: Int) {
|
||||
val dialog = ChangelogDialog(context);
|
||||
fun showChangelogDialog(context: Context, lastVersion: Int, changelogs: Map<Int, String>? = null) {
|
||||
val dialog = ChangelogDialog(context, changelogs);
|
||||
registerDialogOpened(dialog);
|
||||
dialog.setOnDismissListener { registerDialogClosed(dialog) };
|
||||
dialog.show();
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -52,6 +52,7 @@ class SourcePluginConfig(
|
|||
var allowAllHttpHeaderAccess: Boolean = false,
|
||||
var maxDownloadParallelism: Int = 0,
|
||||
var reduceFunctionsInLimitedVersion: Boolean = false,
|
||||
var changelog: HashMap<String, List<String>>? = 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<SourcePluginConfig>(json);
|
||||
|
|
|
@ -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<Int, String>? = 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<Int, String?>(StateApp.instance.scopeGetter, { version -> StateUpdate.instance.downloadChangelog(_managedHttpClient, version) })
|
||||
private val _taskDownloadChangelog = TaskHandler<Int, String?>(StateApp.instance.scopeGetter, { version -> if(changelogs == null)
|
||||
StateUpdate.instance.downloadChangelog(_managedHttpClient, version)
|
||||
else
|
||||
changelogs[version]
|
||||
})
|
||||
.success { setChangelog(it); }
|
||||
.exception<Throwable> {
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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<IPlatformContent>): List<IPlatformContent> {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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<Throwable> {
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String>()
|
||||
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<IPlatformVideo>?, canEdit: Boolean) {
|
||||
|
|
|
@ -55,7 +55,7 @@ class ToastView : LinearLayout {
|
|||
translationY = 20.dp(context.resources).toFloat();
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.setDuration(700)
|
||||
.setDuration(300)
|
||||
.translationY(0f)
|
||||
.start();
|
||||
}
|
||||
|
|
|
@ -93,6 +93,26 @@
|
|||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_changelog"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:textAlignment="textStart"
|
||||
android:text="Changelog"
|
||||
android:textSize="9.5sp"
|
||||
android:textColor="@color/white"
|
||||
android:fontFamily="monospace"
|
||||
android:scrollbars="vertical"
|
||||
|
||||
android:background="@color/black"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
<string name="home">Home</string>
|
||||
<string name="progress_bar">Progress Bar</string>
|
||||
<string name="progress_bar_description">If a historical progress bar should be shown</string>
|
||||
<string name="hide_hidden_from_search">Hide hidden from home in search</string>
|
||||
<string name="hide_hidden_from_search_description">Hide videos and creators hidden from home also in search results</string>
|
||||
<string name="recommendations">Recommendations</string>
|
||||
<string name="more">More</string>
|
||||
<string name="playlists">Playlists</string>
|
||||
|
@ -195,6 +197,7 @@
|
|||
<string name="connected_to">Connected to</string>
|
||||
<string name="volume">Volume</string>
|
||||
<string name="changelog">Changelog</string>
|
||||
<string name="changelog_plugin_description">Shows available changelogs for current and past versions</string>
|
||||
<string name="some_example_changelog">Some example changelog.</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="next">Next</string>
|
||||
|
@ -398,8 +401,8 @@
|
|||
<string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string>
|
||||
<string name="allow_under_cutout">Allow video under cutout</string>
|
||||
<string name="allow_under_cutout_description">Allow video to go underneath the screen cutout in full screen.\nMay require restart</string>
|
||||
<string name="autoplay">Enable autoplay by default</string>
|
||||
<string name="autoplay_description">Autoplay will be enabled by default whenever you watch a video</string>
|
||||
<string name="autoplay">Autoplay next video by default</string>
|
||||
<string name="autoplay_description">Autoplay next video will be enabled by default whenever you watch a video</string>
|
||||
<string name="allow_full_screen_portrait">Allow full-screen portrait when watching horizontal videos</string>
|
||||
<string name="delete_watchlist_on_finish">Delete from WatchLater when watched</string>
|
||||
<string name="delete_watchlist_on_finish_description">After you leave a video that you mostly watched, it will be removed from watch later.</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue