From 116dc90d21c602885057f666a6852286c678c0e1 Mon Sep 17 00:00:00 2001 From: Koen Date: Tue, 14 Nov 2023 14:18:17 +0100 Subject: [PATCH 1/6] Changed the way the changelog is written. --- deploy-stable.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy-stable.sh b/deploy-stable.sh index cf1a805b..ba965acf 100644 --- a/deploy-stable.sh +++ b/deploy-stable.sh @@ -25,7 +25,7 @@ cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk $DOCU VERSION=$(git describe --tags) echo $VERSION > $DOCUMENT_ROOT/version.txt mkdir -p $DOCUMENT_ROOT/changelogs -git tag -l $VERSION -n1000 | awk '{$1=""; print $0}' | sed -e 's/^[ \t]*//' > $DOCUMENT_ROOT/changelogs/$VERSION +git tag -l --format='%(contents)' $VERSION > $DOCUMENT_ROOT/changelogs/$VERSION # Notify Cloudflare to wipe the CDN cache echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..." From d34cb0f9c14c811553cc812f60cd43a0a5ce3916 Mon Sep 17 00:00:00 2001 From: Koen Date: Tue, 14 Nov 2023 14:42:41 +0100 Subject: [PATCH 2/6] Added support for long-press gesture to open options menu. --- .../mainactivity/main/ContentFeedView.kt | 69 +++++++++++++------ .../main/ContentSearchResultsFragment.kt | 7 ++ .../mainactivity/main/HomeFragment.kt | 7 ++ .../main/PlaylistSearchResultsFragment.kt | 7 ++ .../main/SubscriptionsFeedFragment.kt | 7 ++ .../adapters/PreviewContentListAdapter.kt | 2 + .../views/adapters/PreviewVideoView.kt | 15 +++- .../views/adapters/PreviewVideoViewHolder.kt | 2 + .../overlays/slideup/SlideUpMenuOverlay.kt | 20 ++++-- 9 files changed, 111 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt index 2a827322..bccbe8d5 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -27,6 +27,7 @@ import com.futo.platformplayer.views.adapters.InsertedViewHolder import com.futo.platformplayer.views.adapters.PreviewNestedVideoViewHolder import com.futo.platformplayer.views.adapters.PreviewVideoViewHolder import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay import kotlin.math.floor abstract class ContentFeedView : FeedView, ContentPreviewViewHolder> where TFragment : MainFragment { @@ -37,6 +38,7 @@ abstract class ContentFeedView : FeedView, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) { @@ -70,26 +72,8 @@ abstract class ContentFeedView : FeedView(it) }; adapter.onAddToClicked.subscribe(this) { content -> //TODO: Reconstruct search video from detail if search is null - _overlayContainer.let { - if(content is IPlatformVideo) - UISlideOverlays.showVideoOptionsOverlay(content, it, SlideUpMenuItem(context, R.drawable.ic_visibility_off, context.getString(R.string.hide), context.getString(R.string.hide_from_home), "hide", - { StateMeta.instance.addHiddenVideo(content.url); - if (fragment is HomeFragment) { - val removeIndex = recyclerData.results.indexOf(content); - if (removeIndex >= 0) { - recyclerData.results.removeAt(removeIndex); - recyclerData.adapter.notifyItemRemoved(recyclerData.adapter.childToParentPosition(removeIndex)); - } - } - }), - SlideUpMenuItem(context, R.drawable.ic_playlist, context.getString(R.string.play_feed_as_queue), context.getString(R.string.play_entire_feed), "playFeed", - { - val newQueue = listOf(content) + recyclerData.results - .filterIsInstance() - .filter { it != content }; - StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false); - }) - ); + if(content is IPlatformVideo) { + showVideoOptionsOverlay(content) } }; adapter.onAddToQueueClicked.subscribe(this) { @@ -99,6 +83,50 @@ abstract class ContentFeedView : FeedView= 0) { + recyclerData.results.removeAt(removeIndex); + recyclerData.adapter.notifyItemRemoved(recyclerData.adapter.childToParentPosition(removeIndex)); + } + } + }), + SlideUpMenuItem(context, R.drawable.ic_playlist, context.getString(R.string.play_feed_as_queue), context.getString(R.string.play_entire_feed), "playFeed", + { + val newQueue = listOf(content) + recyclerData.results + .filterIsInstance() + .filter { it != content }; + StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false); + }) + ); + } } private fun detachAdapterEvents() { @@ -108,6 +136,7 @@ abstract class ContentFeedView : FeedView, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) { 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 523eb110..1ed56d87 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 @@ -62,6 +62,13 @@ class ContentSearchResultsFragment : MainFragment() { _view = null; } + override fun onBackPressed(): Boolean { + if (_view?.onBackPressed() == true) + return true + + return super.onBackPressed() + } + fun setPreviewsEnabled(previewsEnabled: Boolean) { _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.search.previewFeedItems); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt index 1dcc4084..ebf5d56e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -66,6 +66,13 @@ class HomeFragment : MainFragment() { return view; } + override fun onBackPressed(): Boolean { + if (_view?.onBackPressed() == true) + return true + + return super.onBackPressed() + } + override fun onDestroyMainView() { super.onDestroyMainView(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt index 03536707..bf42b2d4 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt @@ -44,6 +44,13 @@ class PlaylistSearchResultsFragment : MainFragment() { return view; } + override fun onBackPressed(): Boolean { + if (_view?.onBackPressed() == true) + return true + + return super.onBackPressed() + } + override fun onDestroyMainView() { super.onDestroyMainView(); _view?.cleanup(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index efb5e021..dea16d7c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -80,6 +80,13 @@ class SubscriptionsFeedFragment : MainFragment() { } } + override fun onBackPressed(): Boolean { + if (_view?.onBackPressed() == true) + return true + + return super.onBackPressed() + } + fun setPreviewsEnabled(previewsEnabled: Boolean) { _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.subscriptions.previewFeedItems); } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt index 1e370010..2924e7e1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt @@ -32,6 +32,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader(); val onAddToClicked = Event1(); val onAddToQueueClicked = Event1(); + val onLongPress = Event1(); private var _taskLoadContent = TaskHandler, Pair>( StateApp.instance.scopeGetter, { (viewHolder, video) -> @@ -93,6 +94,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader(); + val onLongPress = Event1(); val onChannelClicked = Event1(); val onAddToClicked = Event1(); val onAddToQueueClicked = Event1(); @@ -119,7 +120,13 @@ open class PreviewVideoView : LinearLayout { this._exoPlayer = exoPlayer - setOnClickListener { onOpenClicked() }; + setOnLongClickListener { + onLongPress() + true + }; + setOnClickListener { + onOpenClicked() + }; _imageChannel.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; _textChannelName.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; _textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; @@ -145,6 +152,12 @@ open class PreviewVideoView : LinearLayout { } } + protected open fun onLongPress() { + currentVideo?.let { + onLongPress.emit(it); + } + } + open fun bind(content: IPlatformContent) { _taskLoadProfile.cancel(); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt index 8fed1d82..e1ea40e8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt @@ -17,6 +17,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder { val onChannelClicked = Event1(); val onAddToClicked = Event1(); val onAddToQueueClicked = Event1(); + val onLongPress = Event1(); //val context: Context; val currentVideo: IPlatformVideo? get() = view.currentVideo; @@ -30,6 +31,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder { view.onChannelClicked.subscribe(onChannelClicked::emit); view.onAddToClicked.subscribe(onAddToClicked::emit); view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit); + view.onLongPress.subscribe(onLongPress::emit); } diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt index fcac9eed..cc8e30f1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt @@ -134,6 +134,10 @@ class SlideUpMenuOverlay : RelativeLayout { } fun show(){ + if (isVisible) { + return; + } + isVisible = true; _container?.post { _container?.visibility = View.VISIBLE; @@ -146,8 +150,8 @@ class SlideUpMenuOverlay : RelativeLayout { _viewBackground.alpha = 0f; val animations = arrayListOf(); - animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(500)); - animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(500)); + animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(ANIMATION_DURATION_MS)); + animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(ANIMATION_DURATION_MS)); val animatorSet = AnimatorSet(); animatorSet.playTogether(animations); @@ -159,11 +163,15 @@ class SlideUpMenuOverlay : RelativeLayout { } fun hide(animate: Boolean = true){ + if (!isVisible) { + return + } + isVisible = false; if (_animated && animate) { val animations = arrayListOf(); - animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 1.0f, 0.0f).setDuration(500)); - animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", 0.0f, _viewOverlayContainer.measuredHeight.toFloat()).setDuration(500)); + animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 1.0f, 0.0f).setDuration(ANIMATION_DURATION_MS)); + animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", 0.0f, _viewOverlayContainer.measuredHeight.toFloat()).setDuration(ANIMATION_DURATION_MS)); val animatorSet = AnimatorSet(); animatorSet.doOnEnd { @@ -180,4 +188,8 @@ class SlideUpMenuOverlay : RelativeLayout { _container?.visibility = View.GONE; } } + + companion object { + private const val ANIMATION_DURATION_MS = 350L + } } \ No newline at end of file From 9c1b543ed6df21f207a62d03b6e44a70cfad328f Mon Sep 17 00:00:00 2001 From: Koen Date: Tue, 14 Nov 2023 14:58:24 +0100 Subject: [PATCH 3/6] Added 'Add to new playlist' button in options menu. --- .../futo/platformplayer/UISlideOverlays.kt | 36 ++++++++++ .../mainactivity/main/PlaylistsFragment.kt | 69 +++++++++++-------- app/src/main/res/values/strings.xml | 2 + 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 791aef3f..ff0fb916 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -5,6 +5,8 @@ import android.graphics.Color import android.util.TypedValue import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageButton import android.widget.LinearLayout import android.widget.TextView import com.futo.platformplayer.api.http.ManagedHttpClient @@ -369,6 +371,33 @@ class UISlideOverlays { return overlay; } + fun showCreatePlaylistOverlay(container: ViewGroup, onCreate: (String) -> Unit): SlideUpMenuOverlay { + 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; + if (text.isBlank()) { + return@subscribe; + } + + addPlaylistOverlay.hide(); + nameInput.deactivate(); + nameInput.clear(); + onCreate(text) + }; + + addPlaylistOverlay.onCancel.subscribe { + nameInput.deactivate(); + nameInput.clear(); + }; + + addPlaylistOverlay.show(); + nameInput.activate(); + + return addPlaylistOverlay + } + fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, vararg actions: SlideUpMenuItem): SlideUpMenuOverlay { val items = arrayListOf(); val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist(); @@ -407,6 +436,13 @@ class UISlideOverlays { )); val playlistItems = arrayListOf(); + playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, container.context.getString(R.string.new_playlist), container.context.getString(R.string.add_to_new_playlist), "add_to_new_playlist", { + showCreatePlaylistOverlay(container) { + val playlist = Playlist(it, arrayListOf(SerializedPlatformVideo.fromVideo(video))); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + }; + }, false)) + for (playlist in allPlaylists) { playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, "${container.context.getString(R.string.add_to)} " + playlist.name + "", "${playlist.videos.size} " + container.context.getString(R.string.videos), "", { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt index e0d7c68b..b1179940 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.R +import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.assume import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment @@ -54,6 +55,14 @@ class PlaylistsFragment : MainFragment() { _view?.onShown(parameter, isBack); } + override fun onBackPressed(): Boolean { + if (_view?.onBackPressed() == true) { + return true; + } + + return super.onBackPressed() + } + @SuppressLint("ViewConstructor") class PlaylistsView : LinearLayout { private val _fragment: PlaylistsFragment; @@ -64,6 +73,7 @@ class PlaylistsFragment : MainFragment() { private var _adapterWatchLater: VideoListHorizontalAdapter; private var _adapterPlaylist: PlaylistsAdapter; private var _layoutWatchlist: ConstraintLayout; + private var _slideUpOverlay: SlideUpMenuOverlay? = null; constructor(fragment: PlaylistsFragment, inflater: LayoutInflater) : super(inflater.context) { _fragment = fragment; @@ -92,41 +102,24 @@ class PlaylistsFragment : MainFragment() { recyclerPlaylists.adapter = _adapterPlaylist; recyclerPlaylists.layoutManager = LinearLayoutManager(context); - val nameInput = SlideUpMenuTextInput(context, context.getString(R.string.name)); - val addPlaylistOverlay = SlideUpMenuOverlay(context, findViewById(R.id.overlay_create_playlist), context.getString(R.string.create_new_playlist), context.getString(R.string.ok), false, nameInput); + + + val buttonCreatePlaylist = findViewById(R.id.button_create_playlist); + buttonCreatePlaylist.setOnClickListener { + _slideUpOverlay = UISlideOverlays.showCreatePlaylistOverlay(findViewById(R.id.overlay_create_playlist)) { + val playlist = Playlist(it, arrayListOf()); + playlists.add(0, playlist); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + + _adapterPlaylist.notifyItemInserted(0); + }; + }; _adapterPlaylist.onClick.subscribe { p -> _fragment.navigate(p); }; _adapterPlaylist.onPlay.subscribe { p -> StatePlayer.instance.setPlaylist(p, 0, true); }; - addPlaylistOverlay.onOK.subscribe { - val text = nameInput.text; - if (text.isBlank()) { - return@subscribe; - } - - val playlist = Playlist(text, arrayListOf()); - playlists.add(0, playlist); - StatePlaylists.instance.createOrUpdatePlaylist(playlist); - - _adapterPlaylist.notifyItemInserted(0); - addPlaylistOverlay.hide(); - nameInput.deactivate(); - nameInput.clear(); - }; - - addPlaylistOverlay.onCancel.subscribe { - nameInput.deactivate(); - nameInput.clear(); - }; - - val buttonCreatePlaylist = findViewById(R.id.button_create_playlist); - buttonCreatePlaylist.setOnClickListener { - addPlaylistOverlay.show(); - nameInput.activate(); - }; - _appBar = findViewById(R.id.app_bar); _layoutWatchlist = findViewById(R.id.layout_watchlist); @@ -142,12 +135,28 @@ class PlaylistsFragment : MainFragment() { fun onShown(parameter: Any?, isBack: Boolean) { playlists.clear() - playlists.addAll(StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }); + playlists.addAll( + StatePlaylists.instance.getPlaylists() + .sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }); _adapterPlaylist.notifyDataSetChanged(); updateWatchLater(); } + fun onBackPressed(): Boolean { + val slideUpOverlay = _slideUpOverlay; + if (slideUpOverlay != null) { + if (slideUpOverlay.isVisible) { + slideUpOverlay.hide(); + return true; + } + + return false; + } + + return false; + } + private fun updateWatchLater() { val watchList = StatePlaylists.instance.getWatchLater(); if (watchList.isNotEmpty()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0acaf866..38e17479 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -657,6 +657,8 @@ Stopped after {requestCount} to avoid rate limit, click load more to load more. This creator has not setup any monetization features " + Tax" + New playlist + Add to new playlist Recommendations Subscriptions From 2e9405cfdb366d14e77e4d64787beb85bd24ee15 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 15 Nov 2023 11:40:09 +0100 Subject: [PATCH 4/6] Exit full screen swipe is now a down gesture. --- .../views/behavior/GestureControlView.kt | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index 1b577311..d65797a9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -6,6 +6,7 @@ import android.animation.ObjectAnimator import android.content.Context import android.graphics.drawable.Animatable import android.util.AttributeSet +import android.util.Log import android.view.GestureDetector import android.view.LayoutInflater import android.view.MotionEvent @@ -57,8 +58,10 @@ class GestureControlView : LinearLayout { private var _isFullScreen = false; private var _animatorBrightness: ObjectAnimator? = null; private val _layoutControlsFullscreen: FrameLayout; - private var _adjustingFullscreen: Boolean = false; - private var _fullScreenFactor = 1.0f; + private var _adjustingFullscreenUp: Boolean = false; + private var _adjustingFullscreenDown: Boolean = false; + private var _fullScreenFactorUp = 1.0f; + private var _fullScreenFactorDown = 1.0f; val onSeek = Event1(); val onBrightnessAdjusted = Event1(); @@ -100,10 +103,14 @@ class GestureControlView : LinearLayout { _soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _progressSound.progress = _soundFactor; onSoundAdjusted.emit(_soundFactor); - } else if (_adjustingFullscreen) { + } else if (_adjustingFullscreenUp) { val adjustAmount = (distanceY * 2) / height; - _fullScreenFactor = (_fullScreenFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); - _layoutControlsFullscreen.transitionAlpha = _fullScreenFactor; + _fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); + _layoutControlsFullscreen.alpha = _fullScreenFactorUp; + } else if (_adjustingFullscreenDown) { + val adjustAmount = (-distanceY * 2) / height; + _fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); + _layoutControlsFullscreen.alpha = _fullScreenFactorDown; } else { val rx = p0.x / width; val ry = p0.y / height; @@ -114,7 +121,11 @@ class GestureControlView : LinearLayout { } else if (_isFullScreen && rx > 0.6) { startAdjustingSound(); } else if (rx >= 0.4 && rx <= 0.6) { - startAdjustingFullscreen(); + if (_isFullScreen) { + startAdjustingFullscreenDown(); + } else { + startAdjustingFullscreenUp(); + } } } } @@ -180,11 +191,18 @@ class GestureControlView : LinearLayout { stopAdjustingBrightness(); } - if (_adjustingFullscreen && ev.action == MotionEvent.ACTION_UP) { - if (_fullScreenFactor > 0.5) { + if (_adjustingFullscreenUp && ev.action == MotionEvent.ACTION_UP) { + if (_fullScreenFactorUp > 0.5) { onToggleFullscreen.emit(); } - stopAdjustingFullscreen(); + stopAdjustingFullscreenUp(); + } + + if (_adjustingFullscreenDown && ev.action == MotionEvent.ACTION_UP) { + if (_fullScreenFactorDown > 0.5) { + onToggleFullscreen.emit(); + } + stopAdjustingFullscreenDown(); } startHideJobIfNecessary(); @@ -468,15 +486,27 @@ class GestureControlView : LinearLayout { _animatorSound?.start(); } - private fun startAdjustingFullscreen() { - _adjustingFullscreen = true; - _fullScreenFactor = 0f; - _layoutControlsFullscreen.transitionAlpha = 0f; + private fun startAdjustingFullscreenUp() { + _adjustingFullscreenUp = true; + _fullScreenFactorUp = 0f; + _layoutControlsFullscreen.alpha = 0f; _layoutControlsFullscreen.visibility = View.VISIBLE; } - private fun stopAdjustingFullscreen() { - _adjustingFullscreen = false; + private fun stopAdjustingFullscreenUp() { + _adjustingFullscreenUp = false; + _layoutControlsFullscreen.visibility = View.GONE; + } + + private fun startAdjustingFullscreenDown() { + _adjustingFullscreenDown = true; + _fullScreenFactorDown = 0f; + _layoutControlsFullscreen.alpha = 0f; + _layoutControlsFullscreen.visibility = View.VISIBLE; + } + + private fun stopAdjustingFullscreenDown() { + _adjustingFullscreenDown = false; _layoutControlsFullscreen.visibility = View.GONE; } @@ -510,7 +540,7 @@ class GestureControlView : LinearLayout { //onSoundAdjusted.emit(1.0f); stopAdjustingBrightness(); stopAdjustingSound(); - stopAdjustingFullscreen(); + stopAdjustingFullscreenUp(); } _isFullScreen = isFullScreen; From 88f3815585cf4772bf15c9829ebae44e47335f0c Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 15 Nov 2023 11:49:22 +0100 Subject: [PATCH 5/6] When clicking on a video it is added to queue instead of replacing queue. --- .../fragment/mainactivity/main/ContentFeedView.kt | 13 ++++++++----- .../com/futo/platformplayer/states/StatePlayer.kt | 6 ++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt index bccbe8d5..da6329cc 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -166,11 +166,14 @@ abstract class ContentFeedView : FeedView(content.withTimestamp(time)).maximizeVideoDetail(); - else - fragment.navigate(content).maximizeVideoDetail(); + if (StatePlayer.instance.hasQueue) { + StatePlayer.instance.addToQueue(content) + } else { + if (Settings.instance.playback.shouldResumePreview(time)) + fragment.navigate(content.withTimestamp(time)).maximizeVideoDetail(); + else + fragment.navigate(content).maximizeVideoDetail(); + } } else if (content is IPlatformPlaylist) { fragment.navigate(content); } else if (content is IPlatformPost) { 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 028c5b60..54991ed3 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -54,6 +54,12 @@ class StatePlayer { var queueShuffle: Boolean = false private set; + val hasQueue: Boolean get() { + synchronized(_queue) { + return _queue.isNotEmpty() + } + } + val queueName: String get() = _queueName ?: _queueType; //Events From 27c7fb0c1281f72b814c0257bc70798cbbab1695 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 15 Nov 2023 16:18:38 +0000 Subject: [PATCH 6/6] Content length is now set correctly for HttpConstantHandler. --- .../server/handlers/HttpConstantHandler.kt | 1 - .../http/server/handlers/HttpFileHandler.kt | 35 +++++++------------ .../http/server/handlers/HttpProxyHandler.kt | 11 ++++-- .../platformplayer/casting/StateCasting.kt | 2 +- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt index c868b2b7..a58b163d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt @@ -7,7 +7,6 @@ class HttpConstantHandler(method: String, path: String, val content: String, val val headers = this.headers.clone(); if(contentType != null) headers["Content-Type"] = contentType; - headers["Content-Length"] = content.length.toString(); httpContext.respondCode(200, headers, content); } diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt index 89ad16c6..ac72f633 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt @@ -1,14 +1,16 @@ package com.futo.platformplayer.api.http.server.handlers import com.futo.platformplayer.api.http.server.HttpContext +import com.futo.platformplayer.api.http.server.HttpHeaders import com.futo.platformplayer.logging.Logger +import java.io.ByteArrayOutputStream import java.io.File import java.nio.file.Files import java.text.SimpleDateFormat import java.util.* import java.util.zip.GZIPOutputStream -class HttpFileHandler(method: String, path: String, private val contentType: String, private val filePath: String, private val closeAfterRequest: Boolean = false): HttpHandler(method, path) { +class HttpFileHandler(method: String, path: String, private val contentType: String, private val filePath: String): HttpHandler(method, path) { override fun handle(httpContext: HttpContext) { val requestHeaders = httpContext.headers; val responseHeaders = this.headers.clone(); @@ -30,19 +32,13 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str responseHeaders["Content-Disposition"] = "attachment; filename=\"${file.name.replace("\"", "\\\"")}\"" - val acceptEncoding = requestHeaders["Accept-Encoding"] - val shouldGzip = acceptEncoding != null && acceptEncoding.split(',').any { it.trim().equals("gzip", ignoreCase = true) || it == "*" } - if (shouldGzip) { - responseHeaders["Content-Encoding"] = "gzip" - } - val range = requestHeaders["Range"] - var start: Long + val start: Long val end: Long if (range != null && range.startsWith("bytes=")) { val parts = range.substring(6).split("-") start = parts[0].toLong() - end = parts.getOrNull(1)?.toLong() ?: (file.length() - 1) + end = parts.getOrNull(1)?.toLongOrNull() ?: (file.length() - 1) responseHeaders["Content-Range"] = "bytes $start-$end/${file.length()}" } else { start = 0 @@ -51,18 +47,19 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str var totalBytesSent = 0 val contentLength = end - start + 1 - Logger.i(TAG, "Sending $contentLength bytes (start: $start, end: $end, shouldGzip: $shouldGzip)") responseHeaders["Content-Length"] = contentLength.toString() + Logger.i(TAG, "Sending $contentLength bytes (start: $start, end: $end)") file.inputStream().use { inputStream -> - httpContext.respond(if (range == null) 200 else 206, responseHeaders) { responseStream -> + httpContext.respond(if (range != null) 206 else 200, responseHeaders) { responseStream -> try { val buffer = ByteArray(8192) inputStream.skip(start) + var current = start - val outputStream = if (shouldGzip) GZIPOutputStream(responseStream) else responseStream + val outputStream = responseStream while (true) { - val expectedBytesRead = (end - start + 1).coerceAtMost(buffer.size.toLong()); + val expectedBytesRead = (end - current + 1).coerceAtMost(buffer.size.toLong()); val bytesRead = inputStream.read(buffer); if (bytesRead < 0) { Logger.i(TAG, "End of file reached") @@ -73,27 +70,21 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str outputStream.write(buffer, 0, bytesToSend) totalBytesSent += bytesToSend - Logger.v(TAG, "Sent bytes $start-${start + bytesToSend}, totalBytesSent=$totalBytesSent") + Logger.v(TAG, "Sent bytes $current-${current + bytesToSend}, totalBytesSent=$totalBytesSent") - start += bytesToSend.toLong() - if (start >= end) { + current += bytesToSend.toLong() + if (current >= end) { Logger.i(TAG, "Expected amount of bytes sent") break } } Logger.i(TAG, "Finished sending file (segment)") - - if (shouldGzip) (outputStream as GZIPOutputStream).finish() outputStream.flush() } catch (e: Exception) { httpContext.respondCode(500, headers) } } - - if (closeAfterRequest) { - httpContext.keepAlive = false; - } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt index 1756925c..f6774c1e 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.futo.platformplayer.api.http.server.HttpContext import com.futo.platformplayer.api.http.server.HttpHeaders import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.logging.Logger class HttpProxyHandler(method: String, path: String, val targetUrl: String): HttpHandler(method, path) { var content: String? = null; @@ -34,8 +35,8 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt proxyHeaders.put("Referer", targetUrl); val useMethod = if (method == "inherit") context.method else method; - //Logger.i(TAG, "Proxied Request ${useMethod}: ${targetUrl}"); - //Logger.i(TAG, "Headers:" + proxyHeaders.map { "${it.key}: ${it.value}" }.joinToString("\n")); + Logger.i(TAG, "Proxied Request ${useMethod}: ${targetUrl}"); + Logger.i(TAG, "Headers:" + proxyHeaders.map { "${it.key}: ${it.value}" }.joinToString("\n")); val resp = when (useMethod) { "GET" -> _client.get(targetUrl, proxyHeaders); @@ -44,7 +45,7 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt else -> _client.requestMethod(useMethod, targetUrl, proxyHeaders); }; - //Logger.i(TAG, "Proxied Response [${resp.code}]"); + Logger.i(TAG, "Proxied Response [${resp.code}]"); val headersFiltered = HttpHeaders(resp.getHeadersFlat().filter { !_ignoreRequestHeaders.contains(it.key.lowercase()) }); for(newHeader in headers) headersFiltered.put(newHeader.key, newHeader.value); @@ -92,4 +93,8 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt _ignoreRequestHeaders.add("referer"); return this; } + + companion object { + private const val TAG = "HttpProxyHandler" + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index 493218a2..6a3055e7 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -486,7 +486,7 @@ class StateCasting { } if (subtitleSource != null) { _castServer.addHandler( - HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath, true) + HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath) .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); _castServer.addHandler(