Add functionality to open channel shorts in shorts fragment

Changelog: changed
This commit is contained in:
Kai 2025-06-12 14:01:11 -05:00
commit cf96bd1ec0
No known key found for this signature in database
6 changed files with 136 additions and 58 deletions

View file

@ -25,6 +25,7 @@ import com.futo.platformplayer.api.media.structures.IReplacerPager
import com.futo.platformplayer.api.media.structures.MultiPager
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dp
import com.futo.platformplayer.engine.exceptions.PluginException
@ -61,7 +62,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
private var _query: String? = null
private var _searchView: SearchView? = null
val onContentClicked = Event2<IPlatformContent, Long>();
val onContentClicked = Event3<IPlatformContent, Long, Pair<IPager<IPlatformContent>, ArrayList<IPlatformContent>>?>();
val onContentUrlClicked = Event2<String, ContentType>();
val onUrlClicked = Event1<String>();
val onChannelClicked = Event1<PlatformAuthorLink>();
@ -211,7 +212,10 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
_adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar, viewsToPrepend = arrayListOf(searchView)).apply {
this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit);
this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit);
this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit);
this.onContentClicked.subscribe { content, num ->
val results = ArrayList(_results)
this@ChannelContentsFragment.onContentClicked.emit(content, num, Pair(_pager!!, results))
}
this.onChannelClicked.subscribe(this@ChannelContentsFragment.onChannelClicked::emit);
this.onAddToClicked.subscribe(this@ChannelContentsFragment.onAddToClicked::emit);
this.onAddToQueueClicked.subscribe(this@ChannelContentsFragment.onAddToQueueClicked::emit);

View file

@ -392,7 +392,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>(withHistory = false) }),
ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>(withHistory = false) }),
ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>(withHistory = false) }),
ButtonDefinition(5, R.drawable.ic_smart_display, R.drawable.ic_smart_display_filled, R.string.shorts, canToggle = true, { it.currentMain is ShortsFragment }, { it.navigate<ShortsFragment>(withHistory = false) }),
ButtonDefinition(5, R.drawable.ic_smart_display, R.drawable.ic_smart_display_filled, R.string.shorts, canToggle = true, { it.currentMain is ShortsFragment && !(it.currentMain as ShortsFragment).isChannelShortsMode }, { it.navigate<ShortsFragment>(withHistory = false) }),
ButtonDefinition(6, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>(withHistory = false) }),
ButtonDefinition(7, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>(withHistory = false) }),
ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>(withHistory = false) }),

View file

@ -211,6 +211,14 @@ class ChannelFragment : MainFragment() {
}
}
}
adapter.onShortClicked.subscribe { v, _, pagerPair ->
when (v) {
is IPlatformVideo -> {
StatePlayer.instance.clearQueue()
fragment.navigate<ShortsFragment>(Triple(v, pagerPair!!.first, pagerPair.second))
}
}
}
adapter.onAddToClicked.subscribe { content ->
_overlayContainer.let {
if (content is IPlatformVideo) _slideUpOverlay =

View file

@ -160,10 +160,10 @@ class ShortView : FrameLayout {
null
val onResetTriggered = Event0()
val onPlayingToggled = Event1<Boolean>()
val onLikesLoaded = Event3<RatingLikeDislikes, Boolean, Boolean>()
val onLikeDislikeUpdated = Event1<OnLikeDislikeUpdatedArgs>()
val onVideoUpdated = Event1<IPlatformVideo?>()
private val onPlayingToggled = Event1<Boolean>()
private val onLikesLoaded = Event3<RatingLikeDislikes, Boolean, Boolean>()
private val onLikeDislikeUpdated = Event1<OnLikeDislikeUpdatedArgs>()
private val onVideoUpdated = Event1<IPlatformVideo?>()
private val bottomSheet: CommentsModalBottomSheet = CommentsModalBottomSheet()
@ -447,8 +447,7 @@ class ShortView : FrameLayout {
overlay.visibility = VISIBLE
overlay.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(400)
.setInterpolator(OvershootInterpolator(1.2f))
.start()
.setInterpolator(OvershootInterpolator(1.2f)).start()
overlay.postDelayed({
hidePlayPauseIcon()
@ -783,11 +782,11 @@ class ShortView : FrameLayout {
mainFragment.lifecycleScope.launch(Dispatchers.IO) {
try {
Logger.i(CommentsModalBottomSheet.Companion.TAG, "Started backfill")
Logger.i(CommentsModalBottomSheet.TAG, "Started backfill")
args.processHandle.fullyBackfillServersAnnounceExceptions()
Logger.i(CommentsModalBottomSheet.Companion.TAG, "Finished backfill")
Logger.i(CommentsModalBottomSheet.TAG, "Finished backfill")
} catch (e: Throwable) {
Logger.e(CommentsModalBottomSheet.Companion.TAG, "Failed to backfill servers", e)
Logger.e(CommentsModalBottomSheet.TAG, "Failed to backfill servers", e)
}
}
@ -924,7 +923,7 @@ class ShortView : FrameLayout {
const val TAG = "VideoDetailView"
}
class CommentsModalBottomSheet() : BottomSheetDialogFragment() {
class CommentsModalBottomSheet : BottomSheetDialogFragment() {
var mainFragment: MainFragment? = null
private lateinit var containerContent: FrameLayout
@ -968,7 +967,7 @@ class ShortView : FrameLayout {
private lateinit var behavior: BottomSheetBehavior<FrameLayout>
private val _taskLoadPolycentricProfile =
TaskHandler<PlatformID, PolycentricProfile?>(StateApp.instance.scopeGetter, { ApiMethods.getPolycentricProfileByClaim(ApiMethods.SERVER, ApiMethods.FUTO_TRUST_ROOT, it.claimFieldType.toLong(), it.claimType.toLong(), it.value!!) }).success { it -> setPolycentricProfile(it, animate = true) }
TaskHandler<PlatformID, PolycentricProfile?>(StateApp.instance.scopeGetter, { ApiMethods.getPolycentricProfileByClaim(ApiMethods.SERVER, ApiMethods.FUTO_TRUST_ROOT, it.claimFieldType.toLong(), it.claimType.toLong(), it.value!!) }).success { setPolycentricProfile(it, animate = true) }
.exception<Throwable> {
Logger.w(TAG, "Failed to load claims.", it)
}
@ -1024,7 +1023,7 @@ class ShortView : FrameLayout {
val id = c.author.id.value
Logger.i(TAG, "onAuthorClick: $id")
if (id != null && id.startsWith("polycentric://") == true) {
if (id != null && id.startsWith("polycentric://")) {
val navUrl = "https://harbor.social/" + id.substring("polycentric://".length)
mainFragment!!.startActivity(Intent(Intent.ACTION_VIEW, navUrl.toUri()))
}
@ -1229,17 +1228,23 @@ class ShortView : FrameLayout {
buttonPlatform.setTextColor(resources.getColor(if (index == 1) R.color.white else R.color.gray_ac, null))
buttonPolycentric.setTextColor(resources.getColor(if (index == 0) R.color.white else R.color.gray_ac, null))
if (index == null) {
when (index) {
null -> {
addCommentView.visibility = GONE
commentsList.clear()
} else if (index == 0) {
}
0 -> {
addCommentView.visibility = VISIBLE
fetchPolycentricComments()
} else if (index == 1) {
}
1 -> {
addCommentView.visibility = GONE
fetchComments()
}
}
}
private fun fetchComments() {
Logger.i(TAG, "fetchComments")
@ -1252,7 +1257,7 @@ class ShortView : FrameLayout {
Logger.i(TAG, "fetchPolycentricComments")
val video = video
val idValue = video.id.value
if (video.url.isEmpty() != false) {
if (video.url.isEmpty()) {
Logger.w(TAG, "Failed to fetch polycentric comments because url was null")
commentsList.clear()
return

View file

@ -31,8 +31,19 @@ class ShortsFragment : MainFragment() {
private var loadPagerTask: TaskHandler<ShortsFragment, IPager<IPlatformVideo>>? = null
private var nextPageTask: TaskHandler<ShortsFragment, List<IPlatformVideo>>? = null
private var shortsPager: IPager<IPlatformVideo>? = null
private val videos: MutableList<IPlatformVideo> = mutableListOf()
private var mainShortsPager: IPager<IPlatformVideo>? = null
private val mainShorts: MutableList<IPlatformVideo> = mutableListOf()
// the pager to call next on
private var currentShortsPager: IPager<IPlatformVideo>? = null
// the shorts array bound to the ViewPager2 adapter
private val currentShorts: MutableList<IPlatformVideo> = mutableListOf()
private var channelShortsPager: IPager<IPlatformVideo>? = null
private val channelShorts: MutableList<IPlatformVideo> = mutableListOf()
val isChannelShortsMode: Boolean
get() = channelShortsPager != null
private var viewPager: ViewPager2? = null
private lateinit var overlayLoading: FrameLayout
@ -44,6 +55,47 @@ class ShortsFragment : MainFragment() {
loadPager()
}
// we just completely reset the data structure so we want to tell the adapter that
@SuppressLint("NotifyDataSetChanged")
override fun onShown(parameter: Any?, isBack: Boolean) {
super.onShown(parameter, isBack)
if (parameter is Triple<*, *, *>) {
setLoading(false)
channelShorts.clear()
@Suppress("UNCHECKED_CAST") // TODO replace with a strongly typed parameter
channelShorts.addAll(parameter.third as ArrayList<IPlatformVideo>)
@Suppress("UNCHECKED_CAST") // TODO replace with a strongly typed parameter
channelShortsPager = parameter.second as IPager<IPlatformVideo>
currentShorts.clear()
currentShorts.addAll(channelShorts)
currentShortsPager = channelShortsPager
viewPager?.adapter?.notifyDataSetChanged()
viewPager?.post {
viewPager?.currentItem = channelShorts.indexOfFirst {
return@indexOfFirst (parameter.first as IPlatformVideo).id == it.id
}
}
} else if (isChannelShortsMode) {
channelShortsPager = null
channelShorts.clear()
currentShorts.clear()
if (loadPagerTask == null) {
currentShorts.addAll(mainShorts)
currentShortsPager = mainShortsPager
} else {
setLoading(true)
}
viewPager?.adapter?.notifyDataSetChanged()
viewPager?.currentItem = 0
}
}
override fun onCreateMainView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@ -62,8 +114,8 @@ class ShortsFragment : MainFragment() {
Logger.i(TAG, "Creating adapter")
val customViewAdapter =
CustomViewAdapter(videos, layoutInflater, this@ShortsFragment, overlayQualityContainer) {
if (!shortsPager!!.hasMorePages()) {
CustomViewAdapter(currentShorts, layoutInflater, this@ShortsFragment, overlayQualityContainer) {
if (!currentShortsPager!!.hasMorePages()) {
return@CustomViewAdapter
}
nextPage()
@ -81,19 +133,16 @@ class ShortsFragment : MainFragment() {
this.customViewAdapter = customViewAdapter
if (loadPagerTask == null && videos.isEmpty()) {
if (loadPagerTask == null && currentShorts.isEmpty()) {
loadPager()
loadPagerTask!!.success {
applyData()
setLoading(false)
}
} else {
applyData()
}
setLoading(false)
}
private fun applyData() {
val viewPager = viewPager!!
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
fun play(adapter: CustomViewAdapter, position: Int) {
val recycler = (viewPager.getChildAt(0) as RecyclerView)
@ -126,17 +175,13 @@ class ShortsFragment : MainFragment() {
super.onPageScrollStateChanged(state)
if (state == ViewPager2.SCROLL_STATE_IDLE) {
val adapter = (viewPager.adapter as CustomViewAdapter)
val position = adapter.newPosition
if (position == null) {
return
}
val position = adapter.newPosition ?: return
adapter.newPosition = null
play(adapter, position)
}
}
})
setLoading(false)
}
private fun nextPage() {
@ -144,12 +189,17 @@ class ShortsFragment : MainFragment() {
val nextPageTask =
TaskHandler<ShortsFragment, List<IPlatformVideo>>(StateApp.instance.scopeGetter, {
shortsPager!!.nextPage()
currentShortsPager!!.nextPage()
return@TaskHandler shortsPager!!.getResults()
return@TaskHandler currentShortsPager!!.getResults()
}).success { newVideos ->
val prevCount = customViewAdapter!!.itemCount
videos.addAll(newVideos)
currentShorts.addAll(newVideos)
if (isChannelShortsMode) {
channelShorts.addAll(newVideos)
} else {
mainShorts.addAll(newVideos)
}
customViewAdapter!!.notifyItemRangeInserted(prevCount, newVideos.size)
nextPageTask = null
}
@ -170,13 +220,19 @@ class ShortsFragment : MainFragment() {
return@TaskHandler pager
}).success { pager ->
videos.clear()
videos.addAll(pager.getResults())
shortsPager = pager
mainShorts.clear()
mainShorts.addAll(pager.getResults())
mainShortsPager = pager
if (!isChannelShortsMode) {
currentShorts.clear()
currentShorts.addAll(mainShorts)
currentShortsPager = pager
// if the view pager exists go back to the beginning
viewPager?.adapter?.notifyDataSetChanged()
viewPager?.currentItem = 0
}
loadPagerTask = null
}.exception<Throwable> { err ->

View file

@ -9,8 +9,10 @@ import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment
@ -38,6 +40,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
val onContentUrlClicked = Event2<String, ContentType>()
val onUrlClicked = Event1<String>()
val onContentClicked = Event2<IPlatformContent, Long>()
val onShortClicked = Event3<IPlatformContent, Long, Pair<IPager<IPlatformContent>, ArrayList<IPlatformContent>>?>()
val onChannelClicked = Event1<PlatformAuthorLink>()
val onAddToClicked = Event1<IPlatformContent>()
val onAddToQueueClicked = Event1<IPlatformContent>()
@ -81,7 +84,9 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
when (_tabs[position]) {
ChannelTab.VIDEOS -> {
fragment = ChannelContentsFragment.newInstance().apply {
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit)
onContentClicked.subscribe { video, num, _ ->
this@ChannelViewPagerAdapter.onContentClicked.emit(video, num)
}
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit)
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit)
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit)
@ -94,7 +99,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
ChannelTab.SHORTS -> {
fragment = ChannelContentsFragment.newInstance(ResultCapabilities.TYPE_SHORTS).apply {
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit)
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onShortClicked::emit)
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit)
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit)
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit)