mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-03 06:40:19 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
deb3bf6cf7
11 changed files with 783 additions and 350 deletions
|
@ -7,7 +7,7 @@ By using the software, you agree to all of the terms and conditions below.
|
||||||
FUTO Holdings, Inc. (the “Licensor”) grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.
|
FUTO Holdings, Inc. (the “Licensor”) grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
You may use or modify the software for only for non-commercial purposes such as personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, all without any anticipated commercial application.
|
You may use or modify the software only for non-commercial purposes such as personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, all without any anticipated commercial application.
|
||||||
|
|
||||||
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
|
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
private val _taskLoadPolycentricProfile: TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>
|
||||||
//recalculate(position, positionOffset);
|
private val _taskGetChannel: TaskHandler<String, IPlatformChannel>
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflater.inflate(R.layout.fragment_channel, this)
|
||||||
|
_taskLoadPolycentricProfile =
|
||||||
|
TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>({ fragment.lifecycleScope },
|
||||||
|
{ id ->
|
||||||
|
return@TaskHandler PolycentricCache.instance.getProfileAsync(id)
|
||||||
|
}).success { setPolycentricProfile(it, animate = true) }.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); }
|
||||||
private val _taskLoadPolycentricProfile: TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>;
|
|
||||||
private val _taskGetChannel: TaskHandler<String, IPlatformChannel>;
|
|
||||||
|
|
||||||
constructor(fragment: ChannelFragment, inflater: LayoutInflater) : super(inflater.context) {
|
|
||||||
_fragment = fragment;
|
|
||||||
inflater.inflate(R.layout.fragment_channel, this);
|
|
||||||
|
|
||||||
_taskLoadPolycentricProfile = TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>({fragment.lifecycleScope}, { id ->
|
|
||||||
return@TaskHandler PolycentricCache.instance.getProfileAsync(id);
|
|
||||||
})
|
|
||||||
.success { it -> setPolycentricProfile(it, animate = true) }
|
|
||||||
.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); }
|
|
||||||
.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)
|
||||||
|
_buttonSubscriptionSettings.visibility =
|
||||||
|
if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE
|
||||||
setPolycentricProfileOr(parameter) {
|
setPolycentricProfileOr(parameter) {
|
||||||
_textChannel.text = "";
|
_textChannel.text = ""
|
||||||
_textChannelSub.text = "";
|
_textChannelSub.text = ""
|
||||||
_creatorThumbnail.setThumbnail(null, true);
|
_creatorThumbnail.setThumbnail(null, true)
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
.clear(_imageBanner);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
_url = parameter;
|
_url = parameter
|
||||||
loadChannel();
|
loadChannel()
|
||||||
} else if (parameter is SerializedChannel) {
|
}
|
||||||
showChannel(parameter);
|
|
||||||
_url = parameter.url;
|
is SerializedChannel -> {
|
||||||
loadChannel();
|
showChannel(parameter)
|
||||||
} else if (parameter is IPlatformChannel)
|
_url = parameter.url
|
||||||
showChannel(parameter);
|
loadChannel()
|
||||||
else if (parameter is PlatformAuthorLink) {
|
}
|
||||||
|
|
||||||
|
is IPlatformChannel -> showChannel(parameter)
|
||||||
|
is PlatformAuthorLink -> {
|
||||||
setPolycentricProfileOr(parameter.url) {
|
setPolycentricProfileOr(parameter.url) {
|
||||||
_textChannel.text = parameter.name;
|
_textChannel.text = parameter.name
|
||||||
_textChannelSub.text = "";
|
_textChannelSub.text = ""
|
||||||
_creatorThumbnail.setThumbnail(parameter.thumbnail, true);
|
_creatorThumbnail.setThumbnail(parameter.thumbnail, true)
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
.clear(_imageBanner);
|
|
||||||
|
|
||||||
loadPolycentricProfile(parameter.id, parameter.url)
|
loadPolycentricProfile(parameter.id, parameter.url)
|
||||||
};
|
}
|
||||||
|
|
||||||
_url = parameter.url;
|
_url = parameter.url
|
||||||
loadChannel();
|
loadChannel()
|
||||||
} else if (parameter is Subscription) {
|
}
|
||||||
|
|
||||||
|
is Subscription -> {
|
||||||
setPolycentricProfileOr(parameter.channel.url) {
|
setPolycentricProfileOr(parameter.channel.url) {
|
||||||
_textChannel.text = parameter.channel.name;
|
_textChannel.text = parameter.channel.name
|
||||||
_textChannelSub.text = "";
|
_textChannelSub.text = ""
|
||||||
_creatorThumbnail.setThumbnail(parameter.channel.thumbnail, true);
|
_creatorThumbnail.setThumbnail(parameter.channel.thumbnail, true)
|
||||||
Glide.with(_imageBanner)
|
Glide.with(_imageBanner).clear(_imageBanner)
|
||||||
.clear(_imageBanner);
|
|
||||||
|
|
||||||
loadPolycentricProfile(parameter.channel.id, parameter.channel.url)
|
loadPolycentricProfile(parameter.channel.id, parameter.channel.url)
|
||||||
};
|
}
|
||||||
|
|
||||||
_url = parameter.channel.url;
|
_url = parameter.channel.url
|
||||||
loadChannel();
|
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,
|
||||||
|
context.getString(R.string.do_you_want_to_convert_channel_channelname_to_a_playlist)
|
||||||
|
.replace("{channelName}", channel.name),
|
||||||
|
{
|
||||||
UIDialogs.showDialogProgress(context) {
|
UIDialogs.showDialogProgress(context) {
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
_fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
StatePlaylists.instance.createPlaylistFromChannel(channel) { page ->
|
StatePlaylists.instance.createPlaylistFromChannel(channel) { page ->
|
||||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
it.setText("${channel.name}\n" + context.getString(R.string.page) + " $page");
|
it.setText("${channel.name}\n" + context.getString(R.string.page) + " $page")
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch(ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Logger.e(TAG, "Error", ex);
|
Logger.e(TAG, "Error", ex)
|
||||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_convert_channel), ex);
|
UIDialogs.showGeneralErrorDialog(
|
||||||
|
context,
|
||||||
|
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
|
||||||
(_viewPager.adapter as ChannelViewPagerAdapter?)?.let {
|
val playlistPosition = 1
|
||||||
it.getFragment<ChannelContentsFragment>().setChannel(channel);
|
if (supportsPlaylists && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(
|
||||||
it.getFragment<ChannelAboutFragment>().setChannel(channel);
|
ChannelTab.PLAYLISTS.ordinal.toLong()
|
||||||
it.getFragment<ChannelListFragment>().setChannel(channel);
|
)
|
||||||
it.getFragment<ChannelMonetizationFragment>().setChannel(channel);
|
) {
|
||||||
//TODO: Call on other tabs as needed
|
// keep the current tab selected
|
||||||
|
if (_viewPager.currentItem >= playlistPosition) {
|
||||||
|
_viewPager.setCurrentItem(_viewPager.currentItem + 1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.channel = channel;
|
(_viewPager.adapter as ChannelViewPagerAdapter).insert(
|
||||||
|
playlistPosition,
|
||||||
|
ChannelTab.PLAYLISTS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
it.toURLInfoSystemLinkUrl(
|
||||||
|
profile.system.toProto(), it.process, profile.systemState.servers.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate)
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate)
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
_creatorThumbnail.setHarborAvailable(
|
||||||
|
profile != null, animate, profile?.system?.toProto()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()
|
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()?.let {
|
||||||
?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) };
|
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 {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
||||||
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
||||||
|
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlWidevineSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
@ -44,7 +45,7 @@ class VideoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDownloadable(source: IVideoSource) = source is IVideoUrlSource || source is IHLSManifestSource;
|
fun isDownloadable(source: IVideoSource) = source is IVideoUrlSource || source is IHLSManifestSource;
|
||||||
fun isDownloadable(source: IAudioSource) = source is IAudioUrlSource || source is IHLSManifestAudioSource;
|
fun isDownloadable(source: IAudioSource) = (source is IAudioUrlSource || source is IHLSManifestAudioSource) && source !is IAudioUrlWidevineSource
|
||||||
|
|
||||||
fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers);
|
fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers);
|
||||||
fun selectBestVideoSource(sources: Iterable<IVideoSource>, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? {
|
fun selectBestVideoSource(sources: Iterable<IVideoSource>, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val fragment = when (position) {
|
ChannelTab.CHANNELS -> {
|
||||||
0 -> ChannelContentsFragment.newInstance().apply {
|
fragment = ChannelListFragment.newInstance()
|
||||||
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit);
|
.apply { onClickChannel.subscribe(onChannelClicked::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;
|
ChannelTab.PLAYLISTS -> {
|
||||||
return fragment;
|
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) }
|
||||||
|
|
||||||
|
return fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue