From 83843f192da2238cffee2ff76d90e258db2d4f10 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 18:43:15 +0100 Subject: [PATCH] Show total downloaded content duration, Indicator how many subscriptions, save queue as playlist --- .../futo/platformplayer/UISlideOverlays.kt | 30 +++++++++++++++++++ .../mainactivity/main/CreatorsFragment.kt | 9 +++++- .../mainactivity/main/DownloadsFragment.kt | 3 +- .../futo/platformplayer/states/StatePlayer.kt | 7 +++++ .../views/adapters/SubscriptionAdapter.kt | 6 +++- .../views/overlays/QueueEditorOverlay.kt | 21 +++++++++++++ app/src/main/res/layout/fragment_creators.xml | 11 ++++++- app/src/main/res/layout/overlay_queue.xml | 15 ++++++++++ 8 files changed, 98 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 849d1b8c..382f14de 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -79,6 +79,36 @@ class UISlideOverlays { return menu; } + fun showQueueOptionsOverlay(context: Context, container: ViewGroup) { + UISlideOverlays.showOverlay(container, "Queue options", null, { + + }, SlideUpMenuItem(context, R.drawable.ic_playlist, "Save as playlist", "", "Creates a new playlist with queue as videos", null, { + val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name)); + val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput); + + addPlaylistOverlay.onOK.subscribe { + val text = nameInput.text.trim() + if (text.isBlank()) { + return@subscribe; + } + + addPlaylistOverlay.hide(); + nameInput.deactivate(); + nameInput.clear(); + StatePlayer.instance.saveQueueAsPlaylist(text); + UIDialogs.appToast("Playlist [${text}] created"); + }; + + addPlaylistOverlay.onCancel.subscribe { + nameInput.deactivate(); + nameInput.clear(); + }; + + addPlaylistOverlay.show(); + nameInput.activate(); + }, false)); + } + fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup): SlideUpMenuOverlay { val items = arrayListOf(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt index 6efc7a3d..54649ebf 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt @@ -10,6 +10,7 @@ import android.widget.EditText import android.widget.FrameLayout import android.widget.ImageButton import android.widget.Spinner +import android.widget.TextView import androidx.core.widget.addTextChangedListener import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -26,6 +27,7 @@ class CreatorsFragment : MainFragment() { private var _overlayContainer: FrameLayout? = null; private var _containerSearch: FrameLayout? = null; private var _editSearch: EditText? = null; + private var _textMeta: TextView? = null; private var _buttonClearSearch: ImageButton? = null override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -34,6 +36,7 @@ class CreatorsFragment : MainFragment() { val editSearch: EditText = view.findViewById(R.id.edit_search); val buttonClearSearch: ImageButton = view.findViewById(R.id.button_clear_search) _editSearch = editSearch + _textMeta = view.findViewById(R.id.text_meta); _buttonClearSearch = buttonClearSearch buttonClearSearch.setOnClickListener { editSearch.text.clear() @@ -41,7 +44,11 @@ class CreatorsFragment : MainFragment() { _buttonClearSearch?.visibility = View.INVISIBLE; } - val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)); + val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)) { subs -> + _textMeta?.let { + it.text = "${subs.size} creator${if(subs.size > 1) "s" else ""}"; + } + }; adapter.onClick.subscribe { platformUser -> navigate(platformUser) }; adapter.onSettings.subscribe { sub -> _overlayContainer?.let { UISlideOverlays.showSubscriptionOptionsOverlay(sub, it) } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt index 7995d543..440aa235 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt @@ -22,6 +22,7 @@ import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.toHumanBytesSize +import com.futo.platformplayer.toHumanDuration import com.futo.platformplayer.views.AnyInsertedAdapterView import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop import com.futo.platformplayer.views.adapters.viewholders.VideoDownloadViewHolder @@ -215,7 +216,7 @@ class DownloadsFragment : MainFragment() { _listDownloadedHeader.visibility = GONE; } else { _listDownloadedHeader.visibility = VISIBLE; - _listDownloadedMeta.text = "(${downloaded.size} ${context.getString(R.string.videos).lowercase()})"; + _listDownloadedMeta.text = "(${downloaded.size} ${context.getString(R.string.videos).lowercase()}${if(downloaded.size > 0) ", ${downloaded.sumOf { it.duration }.toHumanDuration(false)}" else ""})"; } lastDownloads = downloaded; diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt index 286941c4..b8368ea5 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails 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.constructs.Event1 import com.futo.platformplayer.logging.Logger @@ -130,6 +131,12 @@ class StatePlayer { closeMediaSession(); } + fun saveQueueAsPlaylist(name: String){ + val videos = _queue.toList(); + val playlist = Playlist(name, videos.map { SerializedPlatformVideo.fromVideo(it) }); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + } + //Notifications fun hasMediaSession() : Boolean { return MediaPlaybackService.getService() != null; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt index e3644cc3..ef3f7cb0 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt @@ -16,6 +16,7 @@ class SubscriptionAdapter : RecyclerView.Adapter { private lateinit var _sortedDataset: List; private val _inflater: LayoutInflater; private val _confirmationMessage: String; + private val _onDatasetChanged: ((List)->Unit)?; var onClick = Event1(); var onSettings = Event1(); @@ -30,9 +31,10 @@ class SubscriptionAdapter : RecyclerView.Adapter { updateDataset(); } - constructor(inflater: LayoutInflater, confirmationMessage: String) : super() { + constructor(inflater: LayoutInflater, confirmationMessage: String, onDatasetChanged: ((List)->Unit)? = null) : super() { _inflater = inflater; _confirmationMessage = confirmationMessage; + _onDatasetChanged = onDatasetChanged; StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> if(Looper.myLooper() != Looper.getMainLooper()) StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { updateDataset() } @@ -78,6 +80,8 @@ class SubscriptionAdapter : RecyclerView.Adapter { .filter { (queryLower.isNullOrBlank() || it.channel.name.lowercase().contains(queryLower)) } .toList(); + _onDatasetChanged?.invoke(_sortedDataset); + notifyDataSetChanged(); } diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt index 215e8dcb..edaed188 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt @@ -2,16 +2,26 @@ package com.futo.platformplayer.views.overlays import android.content.Context import android.util.AttributeSet +import android.widget.FrameLayout +import android.widget.ImageView import android.widget.LinearLayout import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.R +import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.views.lists.VideoListEditorView +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput class QueueEditorOverlay : LinearLayout { private val _topbar : OverlayTopbar; private val _editor : VideoListEditorView; + private val _btnSettings: ImageView; + + private val _overlayContainer: FrameLayout; + val onClose = Event0(); @@ -19,6 +29,9 @@ class QueueEditorOverlay : LinearLayout { inflate(context, R.layout.overlay_queue, this) _topbar = findViewById(R.id.topbar); _editor = findViewById(R.id.editor); + _btnSettings = findViewById(R.id.button_settings); + _overlayContainer = findViewById(R.id.overlay_container); + _topbar.onClose.subscribe(this, onClose::emit); _editor.onVideoOrderChanged.subscribe { StatePlayer.instance.setQueueWithExisting(it) } @@ -28,6 +41,10 @@ class QueueEditorOverlay : LinearLayout { } _editor.onVideoClicked.subscribe { v -> StatePlayer.instance.setQueuePosition(v) } + _btnSettings.setOnClickListener { + handleSettings(); + } + _topbar.setInfo(context.getString(R.string.queue), ""); } @@ -40,4 +57,8 @@ class QueueEditorOverlay : LinearLayout { fun cleanup() { _topbar.onClose.remove(this); } + + fun handleSettings() { + UISlideOverlays.showQueueOptionsOverlay(context, _overlayContainer); + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_creators.xml b/app/src/main/res/layout/fragment_creators.xml index 62694f56..a3848565 100644 --- a/app/src/main/res/layout/fragment_creators.xml +++ b/app/src/main/res/layout/fragment_creators.xml @@ -16,7 +16,7 @@ + + diff --git a/app/src/main/res/layout/overlay_queue.xml b/app/src/main/res/layout/overlay_queue.xml index 9dc827fb..4cee7598 100644 --- a/app/src/main/res/layout/overlay_queue.xml +++ b/app/src/main/res/layout/overlay_queue.xml @@ -21,5 +21,20 @@ android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/topbar" app:layout_constraintBottom_toBottomOf="parent" /> + + \ No newline at end of file