mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-03 14:50:49 +00:00
Merge branch 'channel-playlists-ui' into 'master'
add support for channel playlists on the channel page See merge request videostreaming/grayjay!18
This commit is contained in:
commit
c3428a695f
9 changed files with 780 additions and 348 deletions
|
@ -114,7 +114,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
override fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
||||||
_lastPolycentricProfile = polycentricProfile;
|
_lastPolycentricProfile = polycentricProfile;
|
||||||
|
|
||||||
if (polycentricProfile == null) {
|
if (polycentricProfile == null) {
|
||||||
|
|
|
@ -309,7 +309,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
|
||||||
_adapterResults?.setLoading(loading);
|
_adapterResults?.setLoading(loading);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
override fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
||||||
val p = _lastPolycentricProfile;
|
val p = _lastPolycentricProfile;
|
||||||
if (p != null && polycentricProfile != null && p.system == polycentricProfile.system) {
|
if (p != null && polycentricProfile != null && p.system == polycentricProfile.system) {
|
||||||
Logger.i(TAG, "setPolycentricProfile skipped because previous was same");
|
Logger.i(TAG, "setPolycentricProfile skipped because previous was same");
|
||||||
|
|
|
@ -124,7 +124,7 @@ class ChannelListFragment : Fragment, IChannelTabFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
override fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
||||||
_taskLoadChannel.cancel();
|
_taskLoadChannel.cancel();
|
||||||
_lastPolycentricProfile = polycentricProfile;
|
_lastPolycentricProfile = polycentricProfile;
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
|
||||||
_lastChannel = channel;
|
_lastChannel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
override fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
||||||
_lastPolycentricProfile = polycentricProfile
|
_lastPolycentricProfile = polycentricProfile
|
||||||
if (polycentricProfile != null) {
|
if (polycentricProfile != null) {
|
||||||
_supportView?.setPolycentricProfile(polycentricProfile)
|
_supportView?.setPolycentricProfile(polycentricProfile)
|
||||||
|
|
|
@ -0,0 +1,297 @@
|
||||||
|
package com.futo.platformplayer.fragment.channel.tab
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.futo.platformplayer.R
|
||||||
|
import com.futo.platformplayer.Settings
|
||||||
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
|
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.models.playlists.IPlatformPlaylist
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.models.JSPager
|
||||||
|
import com.futo.platformplayer.api.media.structures.IAsyncPager
|
||||||
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
|
import com.futo.platformplayer.api.media.structures.IRefreshPager
|
||||||
|
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.TaskHandler
|
||||||
|
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||||
|
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||||
|
import com.futo.platformplayer.exceptions.ChannelException
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
|
import com.futo.platformplayer.views.FeedStyle
|
||||||
|
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
||||||
|
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
||||||
|
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
|
||||||
|
private var _recyclerResults: RecyclerView? = null
|
||||||
|
private var _llmPlaylist: LinearLayoutManager? = null
|
||||||
|
private var _loading = false
|
||||||
|
private var _pagerParent: IPager<IPlatformPlaylist>? = null
|
||||||
|
private var _pager: IPager<IPlatformPlaylist>? = null
|
||||||
|
private var _channel: IPlatformChannel? = null
|
||||||
|
private var _results: ArrayList<IPlatformContent> = arrayListOf()
|
||||||
|
private var _adapterResults: InsertedViewAdapterWithLoader<ContentPreviewViewHolder>? = null
|
||||||
|
|
||||||
|
val onContentClicked = Event2<IPlatformContent, Long>()
|
||||||
|
val onContentUrlClicked = Event2<String, ContentType>()
|
||||||
|
val onUrlClicked = Event1<String>()
|
||||||
|
val onChannelClicked = Event1<PlatformAuthorLink>()
|
||||||
|
val onAddToClicked = Event1<IPlatformContent>()
|
||||||
|
val onAddToQueueClicked = Event1<IPlatformContent>()
|
||||||
|
val onAddToWatchLaterClicked = Event1<IPlatformContent>()
|
||||||
|
val onLongPress = Event1<IPlatformContent>()
|
||||||
|
|
||||||
|
private fun getPlaylistPager(channel: IPlatformChannel): IPager<IPlatformPlaylist> {
|
||||||
|
Logger.i(TAG, "getPlaylistPager")
|
||||||
|
|
||||||
|
return StatePlatform.instance.getChannelPlaylists(channel.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _taskLoadPlaylists =
|
||||||
|
TaskHandler<IPlatformChannel, IPager<IPlatformPlaylist>>({ lifecycleScope }, {
|
||||||
|
val livePager = getPlaylistPager(it)
|
||||||
|
return@TaskHandler livePager
|
||||||
|
}).success { livePager ->
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
setPager(livePager)
|
||||||
|
}.exception<ScriptCaptchaRequiredException> { }.exception<Throwable> {
|
||||||
|
Logger.w(TAG, "Failed to load initial playlists.", it)
|
||||||
|
UIDialogs.showGeneralRetryErrorDialog(requireContext(),
|
||||||
|
it.message ?: "",
|
||||||
|
it,
|
||||||
|
{ loadNextPage() })
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _nextPageHandler: TaskHandler<IPager<IPlatformPlaylist>, List<IPlatformPlaylist>> =
|
||||||
|
TaskHandler<IPager<IPlatformPlaylist>, List<IPlatformPlaylist>>({ lifecycleScope }, {
|
||||||
|
if (it is IAsyncPager<*>) it.nextPageAsync()
|
||||||
|
else it.nextPage()
|
||||||
|
|
||||||
|
processPagerExceptions(it)
|
||||||
|
return@TaskHandler it.getResults()
|
||||||
|
}).success {
|
||||||
|
setLoading(false)
|
||||||
|
val posBefore = _results.size
|
||||||
|
_results.addAll(it)
|
||||||
|
_adapterResults?.let { adapterResult ->
|
||||||
|
adapterResult.notifyItemRangeInserted(
|
||||||
|
adapterResult.childToParentPosition(
|
||||||
|
posBefore
|
||||||
|
), it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.exception<Throwable> {
|
||||||
|
Logger.w(TAG, "Failed to load next page.", it)
|
||||||
|
UIDialogs.showGeneralRetryErrorDialog(requireContext(),
|
||||||
|
it.message ?: "",
|
||||||
|
it,
|
||||||
|
{ loadNextPage() })
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _scrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val recyclerResults = _recyclerResults ?: return
|
||||||
|
val llmPlaylist = _llmPlaylist ?: return
|
||||||
|
|
||||||
|
val visibleItemCount = recyclerResults.childCount
|
||||||
|
val firstVisibleItem = llmPlaylist.findFirstVisibleItemPosition()
|
||||||
|
val visibleThreshold = 15
|
||||||
|
if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= _results.size) {
|
||||||
|
loadNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setChannel(channel: IPlatformChannel) {
|
||||||
|
val c = _channel
|
||||||
|
if (c != null && c.url == channel.url) {
|
||||||
|
Logger.i(TAG, "setChannel skipped because previous was same")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.i(TAG, "setChannel setChannel=${channel}")
|
||||||
|
|
||||||
|
_taskLoadPlaylists.cancel()
|
||||||
|
|
||||||
|
_channel = channel
|
||||||
|
_results.clear()
|
||||||
|
_adapterResults?.notifyDataSetChanged()
|
||||||
|
|
||||||
|
loadInitial()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_channel_videos, container, false)
|
||||||
|
|
||||||
|
_recyclerResults = view.findViewById(R.id.recycler_videos)
|
||||||
|
|
||||||
|
_adapterResults = PreviewContentListAdapter(
|
||||||
|
view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar
|
||||||
|
).apply {
|
||||||
|
this.onContentUrlClicked.subscribe(this@ChannelPlaylistsFragment.onContentUrlClicked::emit)
|
||||||
|
this.onUrlClicked.subscribe(this@ChannelPlaylistsFragment.onUrlClicked::emit)
|
||||||
|
this.onContentClicked.subscribe(this@ChannelPlaylistsFragment.onContentClicked::emit)
|
||||||
|
this.onChannelClicked.subscribe(this@ChannelPlaylistsFragment.onChannelClicked::emit)
|
||||||
|
this.onAddToClicked.subscribe(this@ChannelPlaylistsFragment.onAddToClicked::emit)
|
||||||
|
this.onAddToQueueClicked.subscribe(this@ChannelPlaylistsFragment.onAddToQueueClicked::emit)
|
||||||
|
this.onAddToWatchLaterClicked.subscribe(this@ChannelPlaylistsFragment.onAddToWatchLaterClicked::emit)
|
||||||
|
this.onLongPress.subscribe(this@ChannelPlaylistsFragment.onLongPress::emit)
|
||||||
|
}
|
||||||
|
|
||||||
|
_llmPlaylist = LinearLayoutManager(view.context)
|
||||||
|
_recyclerResults?.adapter = _adapterResults
|
||||||
|
_recyclerResults?.layoutManager = _llmPlaylist
|
||||||
|
_recyclerResults?.addOnScrollListener(_scrollListener)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_recyclerResults?.removeOnScrollListener(_scrollListener)
|
||||||
|
_recyclerResults = null
|
||||||
|
_pager = null
|
||||||
|
|
||||||
|
_taskLoadPlaylists.cancel()
|
||||||
|
_nextPageHandler.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPager(
|
||||||
|
pager: IPager<IPlatformPlaylist>
|
||||||
|
) {
|
||||||
|
if (_pagerParent != null && _pagerParent is IRefreshPager<*>) {
|
||||||
|
(_pagerParent as IRefreshPager<*>).onPagerError.remove(this)
|
||||||
|
(_pagerParent as IRefreshPager<*>).onPagerChanged.remove(this)
|
||||||
|
_pagerParent = null
|
||||||
|
}
|
||||||
|
if (_pager is IReplacerPager<*>) (_pager as IReplacerPager<*>).onReplaced.remove(this)
|
||||||
|
|
||||||
|
val pagerToSet: IPager<IPlatformPlaylist>?
|
||||||
|
if (pager is IRefreshPager<*>) {
|
||||||
|
_pagerParent = pager
|
||||||
|
pagerToSet = pager.getCurrentPager() as IPager<IPlatformPlaylist>
|
||||||
|
pager.onPagerChanged.subscribe(this) {
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
loadPagerInternal(it as IPager<IPlatformPlaylist>)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "loadPagerInternal failed.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pager.onPagerError.subscribe(this) {
|
||||||
|
Logger.e(TAG, "Search pager failed: ${it.message}", it)
|
||||||
|
if (it is PluginException) UIDialogs.toast("Plugin [${it.config.name}] failed due to:\n${it.message}")
|
||||||
|
else UIDialogs.toast("Plugin failed due to:\n${it.message}")
|
||||||
|
}
|
||||||
|
} else pagerToSet = pager
|
||||||
|
|
||||||
|
loadPagerInternal(pagerToSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPagerInternal(
|
||||||
|
pager: IPager<IPlatformPlaylist>
|
||||||
|
) {
|
||||||
|
if (_pager is IReplacerPager<*>) (_pager as IReplacerPager<*>).onReplaced.remove(this)
|
||||||
|
if (pager is IReplacerPager<*>) {
|
||||||
|
pager.onReplaced.subscribe(this) { oldItem, newItem ->
|
||||||
|
if (_pager != pager) return@subscribe
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
val toReplaceIndex = _results.indexOfFirst { it == oldItem }
|
||||||
|
if (toReplaceIndex >= 0) {
|
||||||
|
_results[toReplaceIndex] = newItem as IPlatformPlaylist
|
||||||
|
_adapterResults?.let {
|
||||||
|
it.notifyItemChanged(it.childToParentPosition(toReplaceIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pager = pager
|
||||||
|
|
||||||
|
processPagerExceptions(pager)
|
||||||
|
|
||||||
|
_results.clear()
|
||||||
|
val toAdd = pager.getResults()
|
||||||
|
_results.addAll(toAdd)
|
||||||
|
_adapterResults?.notifyDataSetChanged()
|
||||||
|
_recyclerResults?.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInitial() {
|
||||||
|
val channel: IPlatformChannel = _channel ?: return
|
||||||
|
setLoading(true)
|
||||||
|
_taskLoadPlaylists.run(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadNextPage() {
|
||||||
|
val pager: IPager<IPlatformPlaylist> = _pager ?: return
|
||||||
|
if (_pager?.hasMorePages() == true) {
|
||||||
|
setLoading(true)
|
||||||
|
_nextPageHandler.run(pager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLoading(loading: Boolean) {
|
||||||
|
_loading = loading
|
||||||
|
_adapterResults?.setLoading(loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processPagerExceptions(pager: IPager<*>) {
|
||||||
|
if (pager is MultiPager<*> && pager.allowFailure) {
|
||||||
|
val ex = pager.getResultExceptions()
|
||||||
|
for (kv in ex) {
|
||||||
|
val jsPager: JSPager<*>? = when (kv.key) {
|
||||||
|
is MultiPager<*> -> (kv.key as MultiPager<*>).findPager { it is JSPager<*> } as JSPager<*>?
|
||||||
|
is JSPager<*> -> kv.key as JSPager<*>
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
context?.let {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
val channel =
|
||||||
|
if (kv.value is ChannelException) (kv.value as ChannelException).channelNameOrUrl else null
|
||||||
|
if (jsPager != null) UIDialogs.toast(
|
||||||
|
it,
|
||||||
|
"Plugin ${jsPager.getPluginConfig().name} failed:\n" + (if (!channel.isNullOrEmpty()) "(${channel}) " else "") + "${kv.value.message}",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
else UIDialogs.toast(it, kv.value.message ?: "", false)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "Failed to show toast.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "PlaylistsFragment"
|
||||||
|
fun newInstance() = ChannelPlaylistsFragment().apply { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
package com.futo.platformplayer.fragment.channel.tab
|
package com.futo.platformplayer.fragment.channel.tab
|
||||||
|
|
||||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
||||||
|
|
||||||
interface IChannelTabFragment {
|
interface IChannelTabFragment {
|
||||||
fun setChannel(channel: IPlatformChannel);
|
fun setChannel(channel: IPlatformChannel)
|
||||||
}
|
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.futo.platformplayer.*
|
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.UISlideOverlays
|
||||||
import com.futo.platformplayer.api.media.PlatformID
|
import com.futo.platformplayer.api.media.PlatformID
|
||||||
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
|
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
|
||||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
|
@ -27,26 +28,32 @@ import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
|
||||||
import com.futo.platformplayer.api.media.models.post.IPlatformPost
|
import com.futo.platformplayer.api.media.models.post.IPlatformPost
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
|
import com.futo.platformplayer.assume
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
|
import com.futo.platformplayer.dp
|
||||||
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
|
import com.futo.platformplayer.fragment.channel.tab.IChannelTabFragment
|
||||||
import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment
|
|
||||||
import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment
|
|
||||||
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
||||||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.SearchType
|
import com.futo.platformplayer.models.SearchType
|
||||||
import com.futo.platformplayer.models.Subscription
|
import com.futo.platformplayer.models.Subscription
|
||||||
import com.futo.platformplayer.polycentric.PolycentricCache
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
|
import com.futo.platformplayer.selectBestImage
|
||||||
|
import com.futo.platformplayer.selectHighestResolutionImage
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.states.StatePlaylists
|
import com.futo.platformplayer.states.StatePlaylists
|
||||||
import com.futo.platformplayer.states.StateSubscriptions
|
import com.futo.platformplayer.states.StateSubscriptions
|
||||||
|
import com.futo.platformplayer.toHumanNumber
|
||||||
|
import com.futo.platformplayer.views.adapters.ChannelTab
|
||||||
import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter
|
import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter
|
||||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||||
import com.futo.platformplayer.views.subscriptions.SubscribeButton
|
import com.futo.platformplayer.views.subscriptions.SubscribeButton
|
||||||
import com.futo.polycentric.core.*
|
import com.futo.polycentric.core.OwnedClaim
|
||||||
|
import com.futo.polycentric.core.PublicKey
|
||||||
|
import com.futo.polycentric.core.SystemState
|
||||||
|
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -55,459 +62,530 @@ import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PolycentricProfile(val system: PublicKey, val systemState: SystemState, val ownedClaims: List<OwnedClaim>);
|
data class PolycentricProfile(
|
||||||
|
val system: PublicKey, val systemState: SystemState, val ownedClaims: List<OwnedClaim>
|
||||||
|
)
|
||||||
|
|
||||||
class ChannelFragment : MainFragment() {
|
class ChannelFragment : MainFragment() {
|
||||||
override val isMainView : Boolean = true;
|
override val isMainView: Boolean = true
|
||||||
override val hasBottomBar: Boolean = true;
|
override val hasBottomBar: Boolean = true
|
||||||
private var _view: ChannelView? = null;
|
private var _view: ChannelView? = null
|
||||||
|
|
||||||
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
||||||
super.onShownWithView(parameter, isBack);
|
super.onShownWithView(parameter, isBack)
|
||||||
_view?.onShown(parameter, isBack);
|
_view?.onShown(parameter, isBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateMainView(
|
||||||
val view = ChannelView(this, inflater);
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
_view = view;
|
): View {
|
||||||
return view;
|
val view = ChannelView(this, inflater)
|
||||||
|
_view = view
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed(): Boolean {
|
override fun onBackPressed(): Boolean {
|
||||||
return _view?.onBackPressed() ?: false;
|
return _view?.onBackPressed() ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyMainView() {
|
override fun onDestroyMainView() {
|
||||||
super.onDestroyMainView();
|
super.onDestroyMainView()
|
||||||
|
|
||||||
_view?.cleanup();
|
_view?.cleanup()
|
||||||
_view = null;
|
_view = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTab(selectedTabIndex: Int) {
|
fun selectTab(tab: ChannelTab) {
|
||||||
_view?.selectTab(selectedTabIndex);
|
_view?.selectTab(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class ChannelView : LinearLayout {
|
class ChannelView
|
||||||
private val _fragment: ChannelFragment;
|
(fragment: ChannelFragment, inflater: LayoutInflater) : LinearLayout(inflater.context) {
|
||||||
|
private val _fragment: ChannelFragment = fragment
|
||||||
|
|
||||||
private var _textChannel: TextView;
|
private var _textChannel: TextView
|
||||||
private var _textChannelSub: TextView;
|
private var _textChannelSub: TextView
|
||||||
private var _creatorThumbnail: CreatorThumbnail;
|
private var _creatorThumbnail: CreatorThumbnail
|
||||||
private var _imageBanner: AppCompatImageView;
|
private var _imageBanner: AppCompatImageView
|
||||||
|
|
||||||
private var _tabs: TabLayout;
|
private var _tabs: TabLayout
|
||||||
private var _viewPager: ViewPager2;
|
private var _viewPager: ViewPager2
|
||||||
private var _tabLayoutMediator: TabLayoutMediator;
|
|
||||||
private var _buttonSubscribe: SubscribeButton;
|
|
||||||
private var _buttonSubscriptionSettings: ImageButton;
|
|
||||||
|
|
||||||
private var _overlayContainer: FrameLayout;
|
// private var _adapter: ChannelViewPagerAdapter;
|
||||||
private var _overlay_loading: LinearLayout;
|
private var _tabLayoutMediator: TabLayoutMediator
|
||||||
private var _overlay_loading_spinner: ImageView;
|
private var _buttonSubscribe: SubscribeButton
|
||||||
|
private var _buttonSubscriptionSettings: ImageButton
|
||||||
|
|
||||||
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
private var _overlayContainer: FrameLayout
|
||||||
|
private var _overlayLoading: LinearLayout
|
||||||
|
private var _overlayLoadingSpinner: ImageView
|
||||||
|
|
||||||
private var _isLoading: Boolean = false;
|
private var _slideUpOverlay: SlideUpMenuOverlay? = null
|
||||||
private var _selectedTabIndex: Int = -1;
|
|
||||||
|
private var _isLoading: Boolean = false
|
||||||
|
private var _selectedTabIndex: Int = -1
|
||||||
var channel: IPlatformChannel? = null
|
var channel: IPlatformChannel? = null
|
||||||
private set;
|
private set
|
||||||
private var _url: String? = null;
|
private var _url: String? = null
|
||||||
|
|
||||||
private val _onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
|
private val _onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {}
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
|
||||||
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
|
||||||
//recalculate(position, positionOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _taskLoadPolycentricProfile: TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>;
|
private val _taskLoadPolycentricProfile: TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>
|
||||||
private val _taskGetChannel: TaskHandler<String, IPlatformChannel>;
|
private val _taskGetChannel: TaskHandler<String, IPlatformChannel>
|
||||||
|
|
||||||
constructor(fragment: ChannelFragment, inflater: LayoutInflater) : super(inflater.context) {
|
init {
|
||||||
_fragment = fragment;
|
inflater.inflate(R.layout.fragment_channel, this)
|
||||||
inflater.inflate(R.layout.fragment_channel, this);
|
_taskLoadPolycentricProfile =
|
||||||
|
TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>({ fragment.lifecycleScope },
|
||||||
_taskLoadPolycentricProfile = TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>({fragment.lifecycleScope}, { id ->
|
{ id ->
|
||||||
return@TaskHandler PolycentricCache.instance.getProfileAsync(id);
|
return@TaskHandler PolycentricCache.instance.getProfileAsync(id)
|
||||||
})
|
}).success { setPolycentricProfile(it, animate = true) }.exception<Throwable> {
|
||||||
.success { it -> setPolycentricProfile(it, animate = true) }
|
Logger.w(TAG, "Failed to load polycentric profile.", it)
|
||||||
.exception<Throwable> {
|
}
|
||||||
Logger.w(TAG, "Failed to load polycentric profile.", it);
|
_taskGetChannel = TaskHandler<String, IPlatformChannel>({ fragment.lifecycleScope },
|
||||||
};
|
{ url -> StatePlatform.instance.getChannelLive(url) }).success { showChannel(it); }
|
||||||
|
|
||||||
_taskGetChannel = TaskHandler<String, IPlatformChannel>({fragment.lifecycleScope}, { url -> StatePlatform.instance.getChannelLive(url) })
|
|
||||||
.success { showChannel(it); }
|
|
||||||
.exception<NoPlatformClientException> {
|
.exception<NoPlatformClientException> {
|
||||||
|
|
||||||
UIDialogs.showDialog(context,
|
UIDialogs.showDialog(
|
||||||
|
context,
|
||||||
R.drawable.ic_sources,
|
R.drawable.ic_sources,
|
||||||
context.getString(R.string.no_source_enabled_to_support_this_channel) + "\n(${_url})", null, null,
|
context.getString(R.string.no_source_enabled_to_support_this_channel) + "\n(${_url})",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
0,
|
0,
|
||||||
UIDialogs.Action("Back", {
|
UIDialogs.Action("Back", {
|
||||||
fragment.close(true);
|
fragment.close(true)
|
||||||
}, UIDialogs.ActionStyle.PRIMARY)
|
}, UIDialogs.ActionStyle.PRIMARY)
|
||||||
);
|
)
|
||||||
|
}.exception<Throwable> {
|
||||||
|
Logger.e(TAG, "Failed to load channel.", it)
|
||||||
|
UIDialogs.showGeneralRetryErrorDialog(
|
||||||
|
context, it.message ?: "", it, { loadChannel() }, null, fragment
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.exception<Throwable> {
|
val tabs: TabLayout = findViewById(R.id.tabs)
|
||||||
Logger.e(TAG, "Failed to load channel.", it);
|
val viewPager: ViewPager2 = findViewById(R.id.view_pager)
|
||||||
UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadChannel() }, null, fragment);
|
_textChannel = findViewById(R.id.text_channel_name)
|
||||||
}
|
_textChannelSub = findViewById(R.id.text_metadata)
|
||||||
|
_creatorThumbnail = findViewById(R.id.creator_thumbnail)
|
||||||
val tabs: TabLayout = findViewById(R.id.tabs);
|
_imageBanner = findViewById(R.id.image_channel_banner)
|
||||||
val viewPager: ViewPager2 = findViewById(R.id.view_pager);
|
_buttonSubscribe = findViewById(R.id.button_subscribe)
|
||||||
_textChannel = findViewById(R.id.text_channel_name);
|
_buttonSubscriptionSettings = findViewById(R.id.button_sub_settings)
|
||||||
_textChannelSub = findViewById(R.id.text_metadata);
|
_overlayLoading = findViewById(R.id.channel_loading_overlay)
|
||||||
_creatorThumbnail = findViewById(R.id.creator_thumbnail);
|
_overlayLoadingSpinner = findViewById(R.id.channel_loader)
|
||||||
_imageBanner = findViewById(R.id.image_channel_banner);
|
_overlayContainer = findViewById(R.id.overlay_container)
|
||||||
_buttonSubscribe = findViewById(R.id.button_subscribe);
|
|
||||||
_buttonSubscriptionSettings = findViewById(R.id.button_sub_settings);
|
|
||||||
_overlay_loading = findViewById(R.id.channel_loading_overlay);
|
|
||||||
_overlay_loading_spinner = findViewById(R.id.channel_loader);
|
|
||||||
_overlayContainer = findViewById(R.id.overlay_container);
|
|
||||||
|
|
||||||
_buttonSubscribe.onSubscribed.subscribe {
|
_buttonSubscribe.onSubscribed.subscribe {
|
||||||
UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer);
|
UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer)
|
||||||
_buttonSubscriptionSettings.visibility = if(_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE;
|
_buttonSubscriptionSettings.visibility =
|
||||||
|
if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
_buttonSubscribe.onUnSubscribed.subscribe {
|
_buttonSubscribe.onUnSubscribed.subscribe {
|
||||||
_buttonSubscriptionSettings.visibility = if(_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE;
|
_buttonSubscriptionSettings.visibility =
|
||||||
|
if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
_buttonSubscriptionSettings.setOnClickListener {
|
_buttonSubscriptionSettings.setOnClickListener {
|
||||||
val url = channel?.url ?: _url ?: return@setOnClickListener;
|
val url = channel?.url ?: _url ?: return@setOnClickListener
|
||||||
val sub = StateSubscriptions.instance.getSubscription(url) ?: return@setOnClickListener;
|
val sub =
|
||||||
UISlideOverlays.showSubscriptionOptionsOverlay(sub, _overlayContainer);
|
StateSubscriptions.instance.getSubscription(url) ?: return@setOnClickListener
|
||||||
};
|
UISlideOverlays.showSubscriptionOptionsOverlay(sub, _overlayContainer)
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Determine if this is really the only solution (isSaveEnabled=false)
|
//TODO: Determine if this is really the only solution (isSaveEnabled=false)
|
||||||
viewPager.isSaveEnabled = false;
|
viewPager.isSaveEnabled = false
|
||||||
viewPager.registerOnPageChangeCallback(_onPageChangeCallback);
|
viewPager.registerOnPageChangeCallback(_onPageChangeCallback)
|
||||||
val adapter = ChannelViewPagerAdapter(fragment.childFragmentManager, fragment.lifecycle);
|
val adapter = ChannelViewPagerAdapter(fragment.childFragmentManager, fragment.lifecycle)
|
||||||
adapter.onChannelClicked.subscribe { c -> fragment.navigate<ChannelFragment>(c) }
|
adapter.onChannelClicked.subscribe { c -> fragment.navigate<ChannelFragment>(c) }
|
||||||
adapter.onContentClicked.subscribe { v, _ ->
|
adapter.onContentClicked.subscribe { v, _ ->
|
||||||
if(v is IPlatformVideo) {
|
when (v) {
|
||||||
StatePlayer.instance.clearQueue();
|
is IPlatformVideo -> {
|
||||||
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail();
|
StatePlayer.instance.clearQueue()
|
||||||
} else if (v is IPlatformPlaylist) {
|
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail()
|
||||||
fragment.navigate<PlaylistFragment>(v);
|
}
|
||||||
} else if (v is IPlatformPost) {
|
|
||||||
fragment.navigate<PostDetailFragment>(v);
|
is IPlatformPlaylist -> {
|
||||||
|
fragment.navigate<PlaylistFragment>(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
is IPlatformPost -> {
|
||||||
|
fragment.navigate<PostDetailFragment>(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.onAddToClicked.subscribe {content ->
|
adapter.onAddToClicked.subscribe { content ->
|
||||||
_overlayContainer.let {
|
_overlayContainer.let {
|
||||||
if(content is IPlatformVideo)
|
if (content is IPlatformVideo) _slideUpOverlay =
|
||||||
_slideUpOverlay = UISlideOverlays.showVideoOptionsOverlay(content, it);
|
UISlideOverlays.showVideoOptionsOverlay(content, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.onAddToQueueClicked.subscribe { content ->
|
adapter.onAddToQueueClicked.subscribe { content ->
|
||||||
if(content is IPlatformVideo) {
|
if (content is IPlatformVideo) {
|
||||||
StatePlayer.instance.addToQueue(content);
|
StatePlayer.instance.addToQueue(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.onAddToWatchLaterClicked.subscribe { content ->
|
adapter.onAddToWatchLaterClicked.subscribe { content ->
|
||||||
if(content is IPlatformVideo) {
|
if (content is IPlatformVideo) {
|
||||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content));
|
StatePlaylists.instance.addToWatchLater(
|
||||||
UIDialogs.toast("Added to watch later\n[${content.name}]");
|
SerializedPlatformVideo.fromVideo(
|
||||||
|
content
|
||||||
|
)
|
||||||
|
)
|
||||||
|
UIDialogs.toast("Added to watch later\n[${content.name}]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.onUrlClicked.subscribe { url ->
|
adapter.onUrlClicked.subscribe { url ->
|
||||||
fragment.navigate<BrowserFragment>(url);
|
fragment.navigate<BrowserFragment>(url)
|
||||||
}
|
}
|
||||||
adapter.onContentUrlClicked.subscribe { url, contentType ->
|
adapter.onContentUrlClicked.subscribe { url, contentType ->
|
||||||
when(contentType) {
|
when (contentType) {
|
||||||
ContentType.MEDIA -> {
|
ContentType.MEDIA -> {
|
||||||
StatePlayer.instance.clearQueue();
|
StatePlayer.instance.clearQueue()
|
||||||
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail();
|
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail()
|
||||||
};
|
}
|
||||||
ContentType.URL -> fragment.navigate<BrowserFragment>(url);
|
|
||||||
else -> {};
|
ContentType.URL -> fragment.navigate<BrowserFragment>(url)
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.onLongPress.subscribe { content ->
|
adapter.onLongPress.subscribe { content ->
|
||||||
_overlayContainer.let {
|
_overlayContainer.let {
|
||||||
if(content is IPlatformVideo)
|
if (content is IPlatformVideo) _slideUpOverlay =
|
||||||
_slideUpOverlay = UISlideOverlays.showVideoOptionsOverlay(content, it);
|
UISlideOverlays.showVideoOptionsOverlay(content, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewPager.adapter = adapter;
|
viewPager.adapter = adapter
|
||||||
|
val tabLayoutMediator = TabLayoutMediator(
|
||||||
val tabLayoutMediator = TabLayoutMediator(tabs, viewPager) { tab, position ->
|
tabs, viewPager, (viewPager.adapter as ChannelViewPagerAdapter)::getTabNames
|
||||||
tab.text = when (position) {
|
)
|
||||||
0 -> "VIDEOS"
|
tabLayoutMediator.attach()
|
||||||
1 -> "CHANNELS"
|
|
||||||
//2 -> "STORE"
|
|
||||||
2 -> "SUPPORT"
|
|
||||||
3 -> "ABOUT"
|
|
||||||
else -> "Unknown $position"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
tabLayoutMediator.attach();
|
|
||||||
|
|
||||||
_tabLayoutMediator = tabLayoutMediator;
|
|
||||||
_tabs = tabs;
|
|
||||||
_viewPager = viewPager;
|
|
||||||
|
|
||||||
|
_tabLayoutMediator = tabLayoutMediator
|
||||||
|
_tabs = tabs
|
||||||
|
_viewPager = viewPager
|
||||||
if (_selectedTabIndex != -1) {
|
if (_selectedTabIndex != -1) {
|
||||||
selectTab(_selectedTabIndex);
|
selectTab(_selectedTabIndex)
|
||||||
}
|
}
|
||||||
|
setLoading(true)
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
fun selectTab(tab: ChannelTab) {
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).getTabPosition(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
_taskLoadPolycentricProfile.cancel();
|
_taskLoadPolycentricProfile.cancel()
|
||||||
_taskGetChannel.cancel();
|
_taskGetChannel.cancel()
|
||||||
_tabLayoutMediator.detach();
|
_tabLayoutMediator.detach()
|
||||||
_viewPager.unregisterOnPageChangeCallback(_onPageChangeCallback);
|
_viewPager.unregisterOnPageChangeCallback(_onPageChangeCallback)
|
||||||
hideSlideUpOverlay();
|
hideSlideUpOverlay()
|
||||||
(_overlay_loading_spinner.drawable as Animatable?)?.stop();
|
(_overlayLoadingSpinner.drawable as Animatable?)?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onShown(parameter: Any?, isBack: Boolean) {
|
fun onShown(parameter: Any?, isBack: Boolean) {
|
||||||
hideSlideUpOverlay();
|
hideSlideUpOverlay()
|
||||||
_taskLoadPolycentricProfile.cancel();
|
_taskLoadPolycentricProfile.cancel()
|
||||||
_selectedTabIndex = -1;
|
_selectedTabIndex = -1
|
||||||
|
|
||||||
if (!isBack || _url == null) {
|
if (!isBack || _url == null) {
|
||||||
_imageBanner.setImageDrawable(null);
|
_imageBanner.setImageDrawable(null)
|
||||||
|
|
||||||
if (parameter is String) {
|
when (parameter) {
|
||||||
_buttonSubscribe.setSubscribeChannel(parameter);
|
is String -> {
|
||||||
_buttonSubscriptionSettings.visibility = if(_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE;
|
_buttonSubscribe.setSubscribeChannel(parameter)
|
||||||
setPolycentricProfileOr(parameter) {
|
_buttonSubscriptionSettings.visibility =
|
||||||
_textChannel.text = "";
|
if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE
|
||||||
_textChannelSub.text = "";
|
setPolycentricProfileOr(parameter) {
|
||||||
_creatorThumbnail.setThumbnail(null, true);
|
_textChannel.text = ""
|
||||||
Glide.with(_imageBanner)
|
_textChannelSub.text = ""
|
||||||
.clear(_imageBanner);
|
_creatorThumbnail.setThumbnail(null, true)
|
||||||
};
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
|
}
|
||||||
|
|
||||||
_url = parameter;
|
_url = parameter
|
||||||
loadChannel();
|
loadChannel()
|
||||||
} else if (parameter is SerializedChannel) {
|
}
|
||||||
showChannel(parameter);
|
|
||||||
_url = parameter.url;
|
|
||||||
loadChannel();
|
|
||||||
} else if (parameter is IPlatformChannel)
|
|
||||||
showChannel(parameter);
|
|
||||||
else if (parameter is PlatformAuthorLink) {
|
|
||||||
setPolycentricProfileOr(parameter.url) {
|
|
||||||
_textChannel.text = parameter.name;
|
|
||||||
_textChannelSub.text = "";
|
|
||||||
_creatorThumbnail.setThumbnail(parameter.thumbnail, true);
|
|
||||||
Glide.with(_imageBanner)
|
|
||||||
.clear(_imageBanner);
|
|
||||||
|
|
||||||
loadPolycentricProfile(parameter.id, parameter.url)
|
is SerializedChannel -> {
|
||||||
};
|
showChannel(parameter)
|
||||||
|
_url = parameter.url
|
||||||
|
loadChannel()
|
||||||
|
}
|
||||||
|
|
||||||
_url = parameter.url;
|
is IPlatformChannel -> showChannel(parameter)
|
||||||
loadChannel();
|
is PlatformAuthorLink -> {
|
||||||
} else if (parameter is Subscription) {
|
setPolycentricProfileOr(parameter.url) {
|
||||||
setPolycentricProfileOr(parameter.channel.url) {
|
_textChannel.text = parameter.name
|
||||||
_textChannel.text = parameter.channel.name;
|
_textChannelSub.text = ""
|
||||||
_textChannelSub.text = "";
|
_creatorThumbnail.setThumbnail(parameter.thumbnail, true)
|
||||||
_creatorThumbnail.setThumbnail(parameter.channel.thumbnail, true);
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
Glide.with(_imageBanner)
|
|
||||||
.clear(_imageBanner);
|
|
||||||
|
|
||||||
loadPolycentricProfile(parameter.channel.id, parameter.channel.url)
|
loadPolycentricProfile(parameter.id, parameter.url)
|
||||||
};
|
}
|
||||||
|
|
||||||
_url = parameter.channel.url;
|
_url = parameter.url
|
||||||
loadChannel();
|
loadChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Subscription -> {
|
||||||
|
setPolycentricProfileOr(parameter.channel.url) {
|
||||||
|
_textChannel.text = parameter.channel.name
|
||||||
|
_textChannelSub.text = ""
|
||||||
|
_creatorThumbnail.setThumbnail(parameter.channel.thumbnail, true)
|
||||||
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
|
|
||||||
|
loadPolycentricProfile(parameter.channel.id, parameter.channel.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
_url = parameter.channel.url
|
||||||
|
loadChannel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loadChannel();
|
loadChannel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTab(selectedTabIndex: Int) {
|
private fun selectTab(selectedTabIndex: Int) {
|
||||||
_selectedTabIndex = selectedTabIndex;
|
_selectedTabIndex = selectedTabIndex
|
||||||
_tabs.selectTab(_tabs.getTabAt(selectedTabIndex));
|
_tabs.selectTab(_tabs.getTabAt(selectedTabIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPolycentricProfile(id: PlatformID, url: String) {
|
private fun loadPolycentricProfile(id: PlatformID, url: String) {
|
||||||
val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(url, true);
|
val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(url, true)
|
||||||
if (cachedPolycentricProfile != null) {
|
if (cachedPolycentricProfile != null) {
|
||||||
setPolycentricProfile(cachedPolycentricProfile, animate = true)
|
setPolycentricProfile(cachedPolycentricProfile, animate = true)
|
||||||
if (cachedPolycentricProfile.expired) {
|
if (cachedPolycentricProfile.expired) {
|
||||||
_taskLoadPolycentricProfile.run(id);
|
_taskLoadPolycentricProfile.run(id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_taskLoadPolycentricProfile.run(id);
|
_taskLoadPolycentricProfile.run(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setLoading(isLoading: Boolean) {
|
private fun setLoading(isLoading: Boolean) {
|
||||||
if (_isLoading == isLoading) {
|
if (_isLoading == isLoading) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_isLoading = isLoading;
|
_isLoading = isLoading
|
||||||
if(isLoading){
|
if (isLoading) {
|
||||||
_overlay_loading.visibility = View.VISIBLE;
|
_overlayLoading.visibility = View.VISIBLE
|
||||||
(_overlay_loading_spinner.drawable as Animatable?)?.start();
|
(_overlayLoadingSpinner.drawable as Animatable?)?.start()
|
||||||
}
|
} else {
|
||||||
else {
|
(_overlayLoadingSpinner.drawable as Animatable?)?.stop()
|
||||||
(_overlay_loading_spinner.drawable as Animatable?)?.stop();
|
_overlayLoading.visibility = View.GONE
|
||||||
_overlay_loading.visibility = View.GONE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBackPressed(): Boolean {
|
fun onBackPressed(): Boolean {
|
||||||
if (_slideUpOverlay != null) {
|
if (_slideUpOverlay != null) {
|
||||||
hideSlideUpOverlay();
|
hideSlideUpOverlay()
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideSlideUpOverlay() {
|
private fun hideSlideUpOverlay() {
|
||||||
_slideUpOverlay?.hide(false);
|
_slideUpOverlay?.hide(false)
|
||||||
_slideUpOverlay = null;
|
_slideUpOverlay = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun loadChannel() {
|
private fun loadChannel() {
|
||||||
val url = _url;
|
val url = _url
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
_taskGetChannel.run(url);
|
_taskGetChannel.run(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showChannel(channel: IPlatformChannel) {
|
private fun showChannel(channel: IPlatformChannel) {
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
|
|
||||||
_fragment.topBar?.onShown(channel);
|
_fragment.topBar?.onShown(channel)
|
||||||
|
|
||||||
val buttons = arrayListOf(Pair(R.drawable.ic_playlist_add) {
|
val buttons = arrayListOf(Pair(R.drawable.ic_playlist_add) {
|
||||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.do_you_want_to_convert_channel_channelname_to_a_playlist).replace("{channelName}", channel.name), {
|
UIDialogs.showConfirmationDialog(context,
|
||||||
UIDialogs.showDialogProgress(context) {
|
context.getString(R.string.do_you_want_to_convert_channel_channelname_to_a_playlist)
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
.replace("{channelName}", channel.name),
|
||||||
try {
|
{
|
||||||
StatePlaylists.instance.createPlaylistFromChannel(channel) { page ->
|
UIDialogs.showDialogProgress(context) {
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
it.setText("${channel.name}\n" + context.getString(R.string.page) + " $page");
|
try {
|
||||||
|
StatePlaylists.instance.createPlaylistFromChannel(channel) { page ->
|
||||||
|
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
it.setText("${channel.name}\n" + context.getString(R.string.page) + " $page")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
} catch (ex: Exception) {
|
||||||
}
|
Logger.e(TAG, "Error", ex)
|
||||||
catch(ex: Exception) {
|
UIDialogs.showGeneralErrorDialog(
|
||||||
Logger.e(TAG, "Error", ex);
|
context,
|
||||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_convert_channel), ex);
|
context.getString(R.string.failed_to_convert_channel),
|
||||||
}
|
ex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
it.hide();
|
it.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
};
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url);
|
val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (plugin != null && plugin.capabilities.hasSearchChannelContents) {
|
if (plugin != null && plugin.capabilities.hasSearchChannelContents) {
|
||||||
buttons.add(Pair(R.drawable.ic_search) {
|
buttons.add(Pair(R.drawable.ic_search) {
|
||||||
_fragment.navigate<SuggestionsFragment>(SuggestionsFragmentData("", SearchType.VIDEO, channel.url));
|
_fragment.navigate<SuggestionsFragment>(
|
||||||
});
|
SuggestionsFragmentData(
|
||||||
|
"", SearchType.VIDEO, channel.url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
_fragment.topBar?.assume<NavigationTopBarFragment>()?.setMenuItems(buttons);
|
_fragment.topBar?.assume<NavigationTopBarFragment>()?.setMenuItems(buttons)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buttonSubscribe.setSubscribeChannel(channel);
|
_buttonSubscribe.setSubscribeChannel(channel)
|
||||||
_buttonSubscriptionSettings.visibility = if(_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE;
|
_buttonSubscriptionSettings.visibility =
|
||||||
_textChannel.text = channel.name;
|
if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE
|
||||||
_textChannelSub.text = if(channel.subscribers > 0) "${channel.subscribers.toHumanNumber()} " + context.getString(R.string.subscribers).lowercase() else "";
|
_textChannel.text = channel.name
|
||||||
|
_textChannelSub.text =
|
||||||
|
if (channel.subscribers > 0) "${channel.subscribers.toHumanNumber()} " + context.getString(
|
||||||
|
R.string.subscribers
|
||||||
|
).lowercase() else ""
|
||||||
|
|
||||||
//TODO: Find a better way to access the adapter fragments..
|
val supportsPlaylists =
|
||||||
|
StatePlatform.instance.getChannelClient(channel.url).capabilities.hasGetChannelPlaylists
|
||||||
|
val playlistPosition = 1
|
||||||
|
if (supportsPlaylists && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
|
||||||
|
ChannelTab.PLAYLISTS.ordinal.toLong()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// keep the current tab selected
|
||||||
|
if (_viewPager.currentItem >= playlistPosition) {
|
||||||
|
_viewPager.setCurrentItem(_viewPager.currentItem + 1, false)
|
||||||
|
}
|
||||||
|
|
||||||
(_viewPager.adapter as ChannelViewPagerAdapter?)?.let {
|
(_viewPager.adapter as ChannelViewPagerAdapter).insert(
|
||||||
it.getFragment<ChannelContentsFragment>().setChannel(channel);
|
playlistPosition,
|
||||||
it.getFragment<ChannelAboutFragment>().setChannel(channel);
|
ChannelTab.PLAYLISTS
|
||||||
it.getFragment<ChannelListFragment>().setChannel(channel);
|
)
|
||||||
it.getFragment<ChannelMonetizationFragment>().setChannel(channel);
|
}
|
||||||
//TODO: Call on other tabs as needed
|
if (!supportsPlaylists && (_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
|
||||||
|
ChannelTab.PLAYLISTS.ordinal.toLong()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// keep the current tab selected
|
||||||
|
if (_viewPager.currentItem >= playlistPosition) {
|
||||||
|
_viewPager.setCurrentItem(_viewPager.currentItem - 1, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).remove(playlistPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.channel = channel;
|
// sets the channel for each tab
|
||||||
|
for (fragment in _fragment.childFragmentManager.fragments) {
|
||||||
|
(fragment as IChannelTabFragment).setChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).channel = channel
|
||||||
|
|
||||||
|
|
||||||
|
_viewPager.adapter!!.notifyDataSetChanged()
|
||||||
|
|
||||||
|
this.channel = channel
|
||||||
|
|
||||||
setPolycentricProfileOr(channel.url) {
|
setPolycentricProfileOr(channel.url) {
|
||||||
_textChannel.text = channel.name;
|
_textChannel.text = channel.name
|
||||||
_creatorThumbnail.setThumbnail(channel.thumbnail, true);
|
_creatorThumbnail.setThumbnail(channel.thumbnail, true)
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).load(channel.banner).crossfade().into(_imageBanner)
|
||||||
.load(channel.banner)
|
|
||||||
.crossfade()
|
|
||||||
.into(_imageBanner);
|
|
||||||
|
|
||||||
_taskLoadPolycentricProfile.run(channel.id);
|
_taskLoadPolycentricProfile.run(channel.id)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setPolycentricProfileOr(url: String, or: () -> Unit) {
|
private fun setPolycentricProfileOr(url: String, or: () -> Unit) {
|
||||||
setPolycentricProfile(null, animate = false);
|
setPolycentricProfile(null, animate = false)
|
||||||
|
|
||||||
val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(url) };
|
val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(url) }
|
||||||
if (cachedProfile != null) {
|
if (cachedProfile != null) {
|
||||||
setPolycentricProfile(cachedProfile, animate = false);
|
setPolycentricProfile(cachedProfile, animate = false)
|
||||||
} else {
|
} else {
|
||||||
or();
|
or()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) {
|
private fun setPolycentricProfile(
|
||||||
val dp_35 = 35.dp(resources)
|
cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean
|
||||||
val profile = cachedPolycentricProfile?.profile;
|
) {
|
||||||
val avatar = profile?.systemState?.avatar?.selectBestImage(dp_35 * dp_35)
|
val dp35 = 35.dp(resources)
|
||||||
?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) };
|
val profile = cachedPolycentricProfile?.profile
|
||||||
|
val avatar = profile?.systemState?.avatar?.selectBestImage(dp35 * dp35)?.let {
|
||||||
if (avatar != null) {
|
it.toURLInfoSystemLinkUrl(
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
profile.system.toProto(), it.process, profile.systemState.servers.toList()
|
||||||
} else {
|
)
|
||||||
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate);
|
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()
|
if (avatar != null) {
|
||||||
?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) };
|
_creatorThumbnail.setThumbnail(avatar, animate)
|
||||||
|
} else {
|
||||||
|
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate)
|
||||||
|
_creatorThumbnail.setHarborAvailable(
|
||||||
|
profile != null, animate, profile?.system?.toProto()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()?.let {
|
||||||
|
it.toURLInfoSystemLinkUrl(
|
||||||
|
profile.system.toProto(), it.process, profile.systemState.servers.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (banner != null) {
|
if (banner != null) {
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).load(banner).crossfade().into(_imageBanner)
|
||||||
.load(banner)
|
|
||||||
.crossfade()
|
|
||||||
.into(_imageBanner);
|
|
||||||
} else {
|
} else {
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).load(channel?.banner).crossfade().into(_imageBanner)
|
||||||
.load(channel?.banner)
|
|
||||||
.crossfade()
|
|
||||||
.into(_imageBanner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
_fragment.topBar?.onShown(profile);
|
_fragment.topBar?.onShown(profile)
|
||||||
_textChannel.text = profile.systemState.username;
|
_textChannel.text = profile.systemState.username
|
||||||
}
|
}
|
||||||
|
|
||||||
(_viewPager.adapter as ChannelViewPagerAdapter?)?.let {
|
// sets the profile for each tab
|
||||||
it.getFragment<ChannelAboutFragment>().setPolycentricProfile(profile);
|
for (fragment in _fragment.childFragmentManager.fragments) {
|
||||||
it.getFragment<ChannelMonetizationFragment>().setPolycentricProfile(profile);
|
(fragment as IChannelTabFragment).setPolycentricProfile(profile)
|
||||||
it.getFragment<ChannelListFragment>().setPolycentricProfile(profile);
|
|
||||||
it.getFragment<ChannelContentsFragment>().setPolycentricProfile(profile);
|
|
||||||
//TODO: Call on other tabs as needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val insertPosition = 1
|
||||||
|
|
||||||
|
//TODO only add channels and support if its setup on the polycentric profile
|
||||||
|
if (profile != null && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
|
||||||
|
ChannelTab.SUPPORT.ordinal.toLong()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).insert(insertPosition, ChannelTab.SUPPORT)
|
||||||
|
}
|
||||||
|
if (profile != null && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
|
||||||
|
ChannelTab.CHANNELS.ordinal.toLong()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).insert(insertPosition, ChannelTab.CHANNELS)
|
||||||
|
}
|
||||||
|
(_viewPager.adapter as ChannelViewPagerAdapter).profile = profile
|
||||||
|
_viewPager.adapter!!.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "ChannelFragment";
|
const val TAG = "ChannelFragment"
|
||||||
fun newInstance() = ChannelFragment().apply { }
|
fun newInstance() = ChannelFragment().apply { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
import com.futo.platformplayer.states.StatePolycentric
|
||||||
import com.futo.platformplayer.toHumanNowDiffString
|
import com.futo.platformplayer.toHumanNowDiffString
|
||||||
import com.futo.platformplayer.toHumanNumber
|
import com.futo.platformplayer.toHumanNumber
|
||||||
|
import com.futo.platformplayer.views.adapters.ChannelTab
|
||||||
import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView
|
import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView
|
||||||
import com.futo.platformplayer.views.comments.AddCommentView
|
import com.futo.platformplayer.views.comments.AddCommentView
|
||||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
|
@ -264,7 +265,7 @@ class PostDetailFragment : MainFragment {
|
||||||
|
|
||||||
_buttonSupport.setOnClickListener {
|
_buttonSupport.setOnClickListener {
|
||||||
val author = _post?.author ?: _postOverview?.author;
|
val author = _post?.author ?: _postOverview?.author;
|
||||||
author?.let { _fragment.navigate<ChannelFragment>(it).selectTab(2); };
|
author?.let { _fragment.navigate<ChannelFragment>(it).selectTab(ChannelTab.SUPPORT); };
|
||||||
};
|
};
|
||||||
|
|
||||||
_buttonStore.setOnClickListener {
|
_buttonStore.setOnClickListener {
|
||||||
|
|
|
@ -5,69 +5,121 @@ import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
|
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.ContentType
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.constructs.Event2
|
import com.futo.platformplayer.constructs.Event2
|
||||||
import com.futo.platformplayer.fragment.channel.tab.*
|
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
|
||||||
|
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
|
||||||
|
import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment
|
||||||
|
import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment
|
||||||
|
import com.futo.platformplayer.fragment.channel.tab.ChannelPlaylistsFragment
|
||||||
|
import com.futo.platformplayer.fragment.channel.tab.IChannelTabFragment
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
|
||||||
class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
|
||||||
private val _cache: Array<Fragment?> = arrayOfNulls(4);
|
|
||||||
|
|
||||||
val onContentUrlClicked = Event2<String, ContentType>();
|
enum class ChannelTab {
|
||||||
val onUrlClicked = Event1<String>();
|
VIDEOS, CHANNELS, PLAYLISTS, SUPPORT, ABOUT
|
||||||
val onContentClicked = Event2<IPlatformContent, Long>();
|
}
|
||||||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
|
||||||
val onAddToClicked = Event1<IPlatformContent>();
|
class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||||
val onAddToQueueClicked = Event1<IPlatformContent>();
|
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
private val _supportedFragments = mutableMapOf(
|
||||||
val onLongPress = Event1<IPlatformContent>();
|
ChannelTab.VIDEOS.ordinal to ChannelTab.VIDEOS, ChannelTab.ABOUT.ordinal to ChannelTab.ABOUT
|
||||||
|
)
|
||||||
|
private val _tabs = arrayListOf(ChannelTab.VIDEOS, ChannelTab.ABOUT)
|
||||||
|
|
||||||
|
var profile: PolycentricProfile? = null
|
||||||
|
var channel: IPlatformChannel? = null
|
||||||
|
|
||||||
|
val onContentUrlClicked = Event2<String, ContentType>()
|
||||||
|
val onUrlClicked = Event1<String>()
|
||||||
|
val onContentClicked = Event2<IPlatformContent, Long>()
|
||||||
|
val onChannelClicked = Event1<PlatformAuthorLink>()
|
||||||
|
val onAddToClicked = Event1<IPlatformContent>()
|
||||||
|
val onAddToQueueClicked = Event1<IPlatformContent>()
|
||||||
|
val onAddToWatchLaterClicked = Event1<IPlatformContent>()
|
||||||
|
val onLongPress = Event1<IPlatformContent>()
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return _tabs[position].ordinal.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun containsItem(itemId: Long): Boolean {
|
||||||
|
return _supportedFragments.containsKey(itemId.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return _cache.size;
|
return _supportedFragments.size
|
||||||
}
|
}
|
||||||
inline fun <reified T:IChannelTabFragment> getFragment(): T {
|
|
||||||
|
|
||||||
//TODO: I have a feeling this can somehow be synced with createFragment so only 1 mapping exists (without a Map<>)
|
fun getTabPosition(tab: ChannelTab): Int {
|
||||||
if(T::class == ChannelContentsFragment::class)
|
return _tabs.indexOf(tab)
|
||||||
return createFragment(0) as T;
|
}
|
||||||
else if(T::class == ChannelListFragment::class)
|
|
||||||
return createFragment(1) as T;
|
fun getTabNames(tab: TabLayout.Tab, position: Int) {
|
||||||
//else if(T::class == ChannelStoreFragment::class)
|
tab.text = _tabs[position].name
|
||||||
// return createFragment(2) as T;
|
}
|
||||||
else if(T::class == ChannelMonetizationFragment::class)
|
|
||||||
return createFragment(2) as T;
|
fun insert(position: Int, tab: ChannelTab) {
|
||||||
else if(T::class == ChannelAboutFragment::class)
|
_supportedFragments[tab.ordinal] = tab
|
||||||
return createFragment(3) as T;
|
_tabs.add(position, tab)
|
||||||
else
|
notifyItemInserted(position)
|
||||||
throw NotImplementedError("Implement other types");
|
}
|
||||||
|
|
||||||
|
fun remove(position: Int) {
|
||||||
|
_supportedFragments.remove(_tabs[position].ordinal)
|
||||||
|
_tabs.removeAt(position)
|
||||||
|
notifyItemRemoved(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
val cachedFragment = _cache[position];
|
val fragment: Fragment
|
||||||
if (cachedFragment != null) {
|
when (_tabs[position]) {
|
||||||
return cachedFragment;
|
ChannelTab.VIDEOS -> {
|
||||||
|
fragment = ChannelContentsFragment.newInstance().apply {
|
||||||
|
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit)
|
||||||
|
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit)
|
||||||
|
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit)
|
||||||
|
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit)
|
||||||
|
onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit)
|
||||||
|
onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit)
|
||||||
|
onAddToWatchLaterClicked.subscribe(this@ChannelViewPagerAdapter.onAddToWatchLaterClicked::emit)
|
||||||
|
onLongPress.subscribe(this@ChannelViewPagerAdapter.onLongPress::emit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelTab.CHANNELS -> {
|
||||||
|
fragment = ChannelListFragment.newInstance()
|
||||||
|
.apply { onClickChannel.subscribe(onChannelClicked::emit) }
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelTab.PLAYLISTS -> {
|
||||||
|
fragment = ChannelPlaylistsFragment.newInstance().apply {
|
||||||
|
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit)
|
||||||
|
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit)
|
||||||
|
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit)
|
||||||
|
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit)
|
||||||
|
onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit)
|
||||||
|
onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit)
|
||||||
|
onAddToWatchLaterClicked.subscribe(this@ChannelViewPagerAdapter.onAddToWatchLaterClicked::emit)
|
||||||
|
onLongPress.subscribe(this@ChannelViewPagerAdapter.onLongPress::emit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelTab.SUPPORT -> {
|
||||||
|
fragment = ChannelMonetizationFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelTab.ABOUT -> {
|
||||||
|
fragment = ChannelAboutFragment.newInstance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
channel?.let { (fragment as IChannelTabFragment).setChannel(it) }
|
||||||
|
profile?.let { (fragment as IChannelTabFragment).setPolycentricProfile(it) }
|
||||||
|
|
||||||
val fragment = when (position) {
|
return fragment
|
||||||
0 -> ChannelContentsFragment.newInstance().apply {
|
|
||||||
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit);
|
|
||||||
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit);
|
|
||||||
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit);
|
|
||||||
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit);
|
|
||||||
onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit);
|
|
||||||
onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit);
|
|
||||||
onAddToWatchLaterClicked.subscribe(this@ChannelViewPagerAdapter.onAddToWatchLaterClicked::emit);
|
|
||||||
onLongPress.subscribe(this@ChannelViewPagerAdapter.onLongPress::emit);
|
|
||||||
};
|
|
||||||
1 -> ChannelListFragment.newInstance().apply { onClickChannel.subscribe(onChannelClicked::emit) };
|
|
||||||
//2 -> ChannelStoreFragment.newInstance();
|
|
||||||
2 -> ChannelMonetizationFragment.newInstance();
|
|
||||||
3 -> ChannelAboutFragment.newInstance();
|
|
||||||
else -> throw IllegalStateException("Invalid tab position $position")
|
|
||||||
};
|
|
||||||
|
|
||||||
_cache[position]= fragment;
|
|
||||||
return fragment;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue