finished UI and interactions

Changelog: added
This commit is contained in:
Kai 2025-04-01 11:25:07 -05:00
parent 76103a2a8c
commit bd87a47551
No known key found for this signature in database
18 changed files with 1953 additions and 302 deletions

View file

@ -160,6 +160,9 @@ android {
dependencies {
implementation 'com.google.dagger:dagger:2.48'
implementation 'androidx.test:monitor:1.7.2'
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation 'com.github.bumptech.glide:compose:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout-compose:1.1.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.48'
//Core

View file

@ -743,6 +743,7 @@ let plugin = {
//To override by plugin
const source = {
getHome() { return new ContentPager([], false, {}); },
getShorts() { return new VideoPager([], false, {}); },
enable(config){ },
disable() {},

View file

@ -12,6 +12,7 @@ import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.models.ImageVariable
@ -35,6 +36,11 @@ interface IPlatformClient {
*/
fun getHome(): IPager<IPlatformContent>
/**
* Gets the shorts feed
*/
fun getShorts(): IPager<IPlatformVideo>
//Search
/**
* Gets search suggestion for the provided query string

View file

@ -22,6 +22,7 @@ import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.internal.JSCallDocs
import com.futo.platformplayer.api.media.platforms.js.internal.JSDocs
import com.futo.platformplayer.api.media.platforms.js.internal.JSDocsParameter
@ -41,6 +42,7 @@ import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager
import com.futo.platformplayer.api.media.platforms.js.models.JSPlaybackTracker
import com.futo.platformplayer.api.media.platforms.js.models.JSPlaylistDetails
import com.futo.platformplayer.api.media.platforms.js.models.JSPlaylistPager
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoPager
import com.futo.platformplayer.api.media.structures.EmptyPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event1
@ -116,6 +118,7 @@ open class JSClient : IPlatformClient {
val enableInSearch get() = descriptor.appSettings.tabEnabled.enableSearch ?: true
val enableInHome get() = descriptor.appSettings.tabEnabled.enableHome ?: true
val enableInShorts get() = descriptor.appSettings.tabEnabled.enableShorts ?: true
fun getSubscriptionRateLimit(): Int? {
val pluginRateLimit = config.subscriptionRateLimit;
@ -288,6 +291,13 @@ open class JSClient : IPlatformClient {
plugin.executeTyped("source.getHome()"));
}
@JSDocs(2, "source.getShorts()", "Gets the Shorts feed of the platform")
override fun getShorts(): IPager<IPlatformVideo> = isBusyWith("getShorts") {
ensureEnabled()
return@isBusyWith JSVideoPager(config, this,
plugin.executeTyped("source.getShorts()"))
}
@JSDocs(3, "source.searchSuggestions(query)", "Gets search suggestions for a given query")
@JSDocsParameter("query", "Query to complete suggestions for")
override fun searchSuggestions(query: String): Array<String> = isBusyWith("searchSuggestions") {

View file

@ -47,6 +47,7 @@ class SourcePluginConfig(
var subscriptionRateLimit: Int? = null,
var enableInSearch: Boolean = true,
var enableInHome: Boolean = true,
var enableInShorts: Boolean = true,
var supportedClaimTypes: List<Int> = listOf(),
var primaryClaimFieldType: Int? = null,
var developerSubmitUrl: String? = null,

View file

@ -103,9 +103,11 @@ class SourcePluginDescriptor {
@FormField(R.string.home, FieldForm.TOGGLE, R.string.show_content_in_home_tab, 1)
var enableHome: Boolean? = null;
@FormField(R.string.search, FieldForm.TOGGLE, R.string.show_content_in_search_results, 2)
var enableSearch: Boolean? = null;
@FormField(R.string.shorts, FieldForm.TOGGLE, R.string.show_content_in_shorts_tab, 3)
var enableShorts: Boolean? = null;
}
@FormField(R.string.ratelimit, "group", R.string.ratelimit_description, 3)
@ -143,6 +145,8 @@ class SourcePluginDescriptor {
tabEnabled.enableHome = config.enableInHome
if(tabEnabled.enableSearch == null)
tabEnabled.enableSearch = config.enableInSearch
if(tabEnabled.enableShorts == null)
tabEnabled.enableShorts = config.enableInShorts
}
}

View file

@ -7,12 +7,12 @@ import java.util.stream.IntStream
* A Content MultiPager that returns results based on a specified distribution
* TODO: Merge all basic distribution pagers
*/
class MultiDistributionContentPager : MultiPager<IPlatformContent> {
class MultiDistributionContentPager<T : IPlatformContent> : MultiPager<T> {
private val dist : HashMap<IPager<IPlatformContent>, Float>;
private val distConsumed : HashMap<IPager<IPlatformContent>, Float>;
private val dist : HashMap<IPager<T>, Float>;
private val distConsumed : HashMap<IPager<T>, Float>;
constructor(pagers : Map<IPager<IPlatformContent>, Float>) : super(pagers.keys.toMutableList()) {
constructor(pagers : Map<IPager<T>, Float>) : super(pagers.keys.toMutableList()) {
val distTotal = pagers.values.sum();
dist = HashMap();
@ -25,7 +25,7 @@ class MultiDistributionContentPager : MultiPager<IPlatformContent> {
}
@Synchronized
override fun selectItemIndex(options: Array<SelectionOption<IPlatformContent>>): Int {
override fun selectItemIndex(options: Array<SelectionOption<T>>): Int {
if(options.size == 0)
return -1;
var bestIndex = 0;
@ -42,6 +42,4 @@ class MultiDistributionContentPager : MultiPager<IPlatformContent> {
distConsumed[options[bestIndex].pager.getPager()] = bestConsumed;
return bestIndex;
}
}

View file

@ -1,15 +1,30 @@
package com.futo.platformplayer.fragment.mainactivity.main
import android.annotation.SuppressLint
import android.graphics.drawable.Animatable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.annotation.OptIn
import androidx.core.view.get
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.states.StatePlatform
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.cancellation.CancellationException
@UnstableApi
class ShortsFragment : MainFragment() {
@ -17,14 +32,25 @@ class ShortsFragment : MainFragment() {
override val isTab: Boolean = true
override val hasBottomBar: Boolean get() = true
private var previousShownView: ShortView? = null
private var loadPagerJob: Job? = null
private var nextPageJob: Job? = null
private lateinit var viewPager: ViewPager2
private var shortsPager: IPager<IPlatformVideo>? = null
private val videos: MutableList<IPlatformVideo> = mutableListOf()
private var viewPager: ViewPager2? = null
private lateinit var overlayLoading: FrameLayout
private lateinit var overlayLoadingSpinner: ImageView
private lateinit var overlayQualityContainer: FrameLayout
private lateinit var customViewAdapter: CustomViewAdapter
private val urls = listOf(
"https://youtube.com/shorts/fHU6dfPHT-o?si=TVCYnt_mvAxWYACZ", "https://youtube.com/shorts/j9LQ0c4MyGk?si=FVlr90UD42y1ZIO0", "https://youtube.com/shorts/Q8LndW9YZvQ?si=mDrSsm-3Uq7IEXAT", "https://youtube.com/shorts/OIS5qHDOOzs?si=RGYeaAH9M-TRuZSr", "https://youtube.com/shorts/1Cp6EbLWVnI?si=N4QqytC48CTnfJra", "https://youtube.com/shorts/fHU6dfPHT-o?si=TVCYnt_mvAxWYACZ", "https://youtube.com/shorts/j9LQ0c4MyGk?si=FVlr90UD42y1ZIO0", "https://youtube.com/shorts/Q8LndW9YZvQ?si=mDrSsm-3Uq7IEXAT", "https://youtube.com/shorts/OIS5qHDOOzs?si=RGYeaAH9M-TRuZSr", "https://youtube.com/shorts/1Cp6EbLWVnI?si=N4QqytC48CTnfJra"
"https://youtube.com/shorts/fHU6dfPHT-o?si=TVCYnt_mvAxWYACZ", "https://youtube.com/shorts/j9LQ0c4MyGk?si=FVlr90UD42y1ZIO0", "https://youtube.com/shorts/Q8LndW9YZvQ?si=mDrSsm-3Uq7IEXAT", "https://www.youtube.com/watch?v=MXHSS-7XcBc", "https://youtube.com/shorts/OIS5qHDOOzs?si=RGYeaAH9M-TRuZSr", "https://youtube.com/shorts/1Cp6EbLWVnI?si=N4QqytC48CTnfJra", "https://youtube.com/shorts/fHU6dfPHT-o?si=TVCYnt_mvAxWYACZ", "https://youtube.com/shorts/j9LQ0c4MyGk?si=FVlr90UD42y1ZIO0", "https://youtube.com/shorts/Q8LndW9YZvQ?si=mDrSsm-3Uq7IEXAT", "https://youtube.com/shorts/OIS5qHDOOzs?si=RGYeaAH9M-TRuZSr", "https://youtube.com/shorts/1Cp6EbLWVnI?si=N4QqytC48CTnfJra"
)
init {
loadPager()
}
override fun onCreateMainView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@ -35,31 +61,146 @@ class ShortsFragment : MainFragment() {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.viewPager)
overlayLoading = view.findViewById(R.id.short_view_loading_overlay)
overlayLoadingSpinner = view.findViewById(R.id.short_view_loader)
overlayQualityContainer = view.findViewById(R.id.videodetail_quality_overview)
customViewAdapter = CustomViewAdapter(urls, layoutInflater, this)
viewPager.adapter = customViewAdapter
setLoading(true)
// TODO something is laggy sometimes when swiping between videos
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
@OptIn(UnstableApi::class)
override fun onPageSelected(position: Int) {
previousShownView?.stop()
if (loadPagerJob?.isActive == false && videos.isEmpty()) {
loadPager()
}
val focusedView =
((viewPager[0] as RecyclerView).findViewHolderForAdapterPosition(position) as CustomViewHolder).shortView
focusedView.play()
loadPagerJob!!.invokeOnCompletion {
customViewAdapter = CustomViewAdapter(videos, layoutInflater, this@ShortsFragment, overlayQualityContainer) {
if (!shortsPager!!.hasMorePages()) {
return@CustomViewAdapter
}
nextPage()
}
customViewAdapter.onResetTriggered.subscribe {
setLoading(true)
loadPager()
loadPagerJob!!.invokeOnCompletion {
setLoading(false)
}
}
val viewPager = viewPager!!
viewPager.adapter = customViewAdapter
// TODO something is laggy sometimes when swiping between videos
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
@OptIn(UnstableApi::class)
override fun onPageSelected(position: Int) {
val adapter = (viewPager.adapter as CustomViewAdapter)
adapter.previousShownView?.stop()
adapter.previousShownView = null
// viewPager.post {
val recycler = (viewPager.getChildAt(0) as RecyclerView)
val viewHolder =
recycler.findViewHolderForAdapterPosition(position) as CustomViewHolder?
if (viewHolder == null) {
adapter.needToPlay = position
} else {
val focusedView = viewHolder.shortView
focusedView.play()
adapter.previousShownView = focusedView
}
// }
}
previousShownView = focusedView
})
setLoading(false)
}
}
private fun nextPage() {
nextPageJob?.cancel()
nextPageJob = CoroutineScope(Dispatchers.Main).launch {
try {
withContext(Dispatchers.IO) {
shortsPager!!.nextPage()
}
} catch (_: CancellationException) {
return@launch
}
})
// if it's been canceled then don't update the results
if (!isActive) {
return@launch
}
val newVideos = shortsPager!!.getResults()
CoroutineScope(Dispatchers.Main).launch {
val prevCount = customViewAdapter.itemCount
videos.addAll(newVideos)
customViewAdapter.notifyItemRangeInserted(prevCount, newVideos.size)
}
}
}
// we just completely reset the data structure so we want to tell the adapter that
@SuppressLint("NotifyDataSetChanged")
private fun loadPager() {
loadPagerJob?.cancel()
// if the view pager exists go back to the beginning
videos.clear()
viewPager?.adapter?.notifyDataSetChanged()
viewPager?.currentItem = 0
loadPagerJob = CoroutineScope(Dispatchers.Main).launch {
// delay(5000)
val pager = try {
withContext(Dispatchers.IO) {
StatePlatform.instance.getShorts()
// StatePlatform.instance.getHome()
// as IPager<IPlatformVideo>
}
} catch (_: CancellationException) {
return@launch
}
// if it's been canceled then don't set the video pager
if (!isActive) {
return@launch
}
videos.clear()
videos.addAll(pager.getResults())
shortsPager = pager
// if the viewPager exists then trigger data changed
viewPager?.adapter?.notifyDataSetChanged()
}
}
private fun setLoading(isLoading: Boolean) {
if (isLoading) {
(overlayLoadingSpinner.drawable as Animatable?)?.start()
overlayLoading.visibility = View.VISIBLE
} else {
overlayLoading.visibility = View.GONE
(overlayLoadingSpinner.drawable as Animatable?)?.stop()
}
}
override fun onPause() {
super.onPause()
previousShownView?.stop()
customViewAdapter.previousShownView?.pause()
}
override fun onResume() {
super.onResume()
}
override fun onStop() {
super.onStop()
customViewAdapter.previousShownView?.stop()
}
companion object {
@ -69,26 +210,51 @@ class ShortsFragment : MainFragment() {
}
class CustomViewAdapter(
private val urls: List<String>, private val inflater: LayoutInflater, private val fragment: MainFragment
private val videos: MutableList<IPlatformVideo>,
private val inflater: LayoutInflater,
private val fragment: MainFragment,
private val overlayQualityContainer: FrameLayout,
private val onNearEnd: () -> Unit,
) : RecyclerView.Adapter<CustomViewHolder>() {
val onResetTriggered = Event0()
var previousShownView: ShortView? = null
var needToPlay: Int? = null
@OptIn(UnstableApi::class)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val shortView = ShortView(inflater, fragment)
val shortView = ShortView(inflater, fragment, overlayQualityContainer)
shortView.onResetTriggered.subscribe {
onResetTriggered.emit()
}
return CustomViewHolder(shortView)
}
@OptIn(UnstableApi::class)
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.shortView.setVideo(urls[position])
holder.shortView.changeVideo(videos[position])
if (position == itemCount - 1) {
onNearEnd()
}
}
@OptIn(UnstableApi::class)
override fun onViewRecycled(holder: CustomViewHolder) {
super.onViewRecycled(holder)
holder.shortView.detach()
holder.shortView.cancel()
}
override fun getItemCount(): Int = urls.size
override fun onViewAttachedToWindow(holder: CustomViewHolder) {
super.onViewAttachedToWindow(holder)
if (holder.absoluteAdapterPosition == needToPlay) {
holder.shortView.play()
needToPlay = null
previousShownView = holder.shortView
}
}
override fun getItemCount(): Int = videos.size
}
@OptIn(UnstableApi::class)

View file

@ -417,6 +417,47 @@ class StatePlatform {
pager.initialize();
return pager;
}
fun getShorts(): IPager<IPlatformVideo> {
Logger.i(TAG, "Platform - getShorts");
var clientIdsOngoing = mutableListOf<String>();
val clients = getSortedEnabledClient().filter { if (it is JSClient) it.enableInShorts else true };
StateApp.instance.scopeOrNull?.let {
it.launch(Dispatchers.Default) {
try {
// plugins that take longer than 5 seconds to load are considered "slow"
delay(5000);
val slowClients = synchronized(clientIdsOngoing) {
return@synchronized clients.filter { clientIdsOngoing.contains(it.id) };
};
for(client in slowClients)
UIDialogs.toast("${client.name} is still loading..\nConsider disabling it for Home", false);
} catch (e: Throwable) {
Logger.e(TAG, "Failed to show toast for slow source.", e)
}
}
}
val pages = clients.parallelStream()
.map {
Logger.i(TAG, "getShorts - ${it.name}")
synchronized(clientIdsOngoing) {
clientIdsOngoing.add(it.id);
}
val shortsResult = it.fromPool(_pagerClientPool).getShorts();
synchronized(clientIdsOngoing) {
clientIdsOngoing.remove(it.id);
}
return@map shortsResult;
}
.asSequence()
.toList()
.associateWith { 1f };
val pager = MultiDistributionContentPager(pages);
pager.initialize();
return pager;
}
suspend fun getHomeRefresh(scope: CoroutineScope): IPager<IPlatformContent> {
Logger.i(TAG, "Platform - getHome (Refresh)");
val clients = getSortedEnabledClient().filter { if (it is JSClient) it.enableInHome else true };

View file

@ -34,7 +34,7 @@ class PlayerManager {
@Synchronized
fun attach(view: PlayerView, stateName: String) {
if(view != _currentView) {
if(view != _currentView || _currentView?.player == null) {
_currentView?.player = null;
switchState(stateName);
view.player = player;

View file

@ -9,6 +9,7 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.animation.LinearInterpolator
import androidx.annotation.OptIn
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.DefaultTimeBar
@ -21,6 +22,7 @@ import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlayer
@UnstableApi
@ -33,6 +35,7 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
}
private var playerAttached = false
// private set;
private val videoView: PlayerView
private val progressBar: DefaultTimeBar
@ -64,12 +67,15 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
}
if (player.isPlaying) {
if (!progressAnimator.isStarted) {
if (progressAnimator.isPaused){
progressAnimator.resume()
}
else if (!progressAnimator.isStarted) {
progressAnimator.start()
}
} else {
if (progressAnimator.isRunning) {
progressAnimator.cancel()
progressAnimator.pause()
}
}
}
@ -81,10 +87,12 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
videoView = findViewById(R.id.video_player)
progressBar = findViewById(R.id.video_player_progress_bar)
player.player.repeatMode = Player.REPEAT_MODE_ONE
progressBar.addListener(object : TimeBar.OnScrubListener {
override fun onScrubStart(timeBar: TimeBar, position: Long) {
if (progressAnimator.isRunning) {
progressAnimator.cancel()
progressAnimator.pause()
}
}
@ -93,7 +101,7 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
if (canceled) {
progressAnimator.currentPlayTime = player.player.currentPosition
progressAnimator.start()
progressAnimator.resume()
return
}
@ -110,9 +118,7 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
interpolator = LinearInterpolator()
addUpdateListener { animation ->
val progress = animation.animatedValue as Float
val duration = animation.duration
progressBar.setPosition((progress * duration).toLong())
progressBar.setPosition(animation.currentPlayTime)
}
}
}
@ -169,11 +175,28 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
@OptIn(UnstableApi::class)
fun setArtwork(drawable: Drawable?) {
if (drawable != null) {
videoView.defaultArtwork = drawable
videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_FILL
videoView.defaultArtwork = drawable
} else {
videoView.defaultArtwork = null
videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_OFF
videoView.defaultArtwork = null
}
}
fun getPlaybackRate(): Float {
return exoPlayer?.player?.playbackParameters?.speed ?: 1.0f
}
fun setPlaybackRate(playbackRate: Float) {
val exoPlayer = exoPlayer?.player
Logger.i(TAG, "setPlaybackRate playbackRate=$playbackRate exoPlayer=${exoPlayer}")
val param = PlaybackParameters(playbackRate)
exoPlayer?.playbackParameters = param
}
// TODO remove stub
fun hideControls(stub: Boolean) {
}
}

View file

@ -1,6 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewPager"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<FrameLayout
android:id="@+id/short_view_loading_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#77000000"
android:elevation="4dp"
android:visibility="gone">
<ImageView
android:id="@+id/short_view_loader"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_vertical|center_horizontal"
android:alpha="0.7"
android:contentDescription="@string/loading"
app:srcCompat="@drawable/ic_loader_animated" />
</FrameLayout>
<FrameLayout
android:id="@+id/videodetail_quality_overview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="100dp"
android:visibility="gone" />
</FrameLayout>

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/standard_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -10,222 +12,401 @@
android:id="@+id/drag_handle"
style="@style/Widget.Material3.BottomSheet.DragHandle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.core.widget.NestedScrollView
android:layout_height="wrap_content"
android:paddingBottom="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
<FrameLayout
android:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/videodetail_container_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
tools:ignore="SpeakableTextPresentCheck">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/layout_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<!--Title Segment-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<!--Title + Meta-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/videodetail_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:fontFamily="@font/inter_medium"
android:textColor="@color/white"
android:textSize="17dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/layout_title_right"
app:layout_constraintTop_toTopOf="parent"
tools:text="Some Text" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/videodetail_meta"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:fontFamily="@font/inter_regular"
android:textColor="#ACACAC"
android:textSize="10dp"
app:layout_constraintLeft_toLeftOf="@id/videodetail_title"
app:layout_constraintRight_toLeftOf="@id/layout_title_right"
app:layout_constraintTop_toBottomOf="@id/videodetail_title"
tools:text="51K views - 3 years ago" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<!--Source Button-->
<LinearLayout
android:id="@+id/layout_title_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="vertical"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<com.futo.platformplayer.views.platform.PlatformIndicator
android:id="@+id/videodetail_platform"
android:layout_width="25dp"
android:layout_height="25dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/layout_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/image_like_icon"
android:layout_width="14dp"
android:layout_height="14dp"
app:srcCompat="@drawable/ic_thumb_up"
app:tint="#ACACAC" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_likes"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:gravity="center_vertical"
android:textColor="#ACACAC"
android:textSize="10dp"
tools:text="500K" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/image_dislike_icon"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginTop="2dp"
app:srcCompat="@drawable/ic_thumb_down"
app:tint="#ACACAC" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/text_dislikes"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:gravity="center_vertical"
android:textColor="#ACACAC"
android:textSize="10dp"
tools:text="500K" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<!--Channel/Subscribe Segment-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="14dp"
android:layout_marginTop="10dp"
android:layout_marginRight="14dp"
android:layout_marginBottom="10dp">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/videodetail_channel_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/button_subscribe"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<com.futo.platformplayer.views.others.CreatorThumbnail
android:id="@+id/creator_thumbnail"
android:layout_width="35dp"
android:layout_height="35dp"
android:contentDescription="@string/cd_creator_thumbnail" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/videodetail_channel_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="10dp"
android:textColor="@color/white"
tools:text="Channel Name" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.Material3.CheckedTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Bottom Sheet Content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/videodetail_channel_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#ACACAC"
android:textSize="9sp"
tools:text="" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.futo.platformplayer.views.subscriptions.SubscribeButton
android:id="@+id/button_subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!--Description-->
<LinearLayout
android:id="@+id/videodetail_description_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:animateLayoutChanges="true"
android:background="@drawable/background_videodetail_description"
android:orientation="vertical"
android:paddingLeft="12dp"
android:paddingTop="3dp"
android:paddingRight="12dp"
android:paddingBottom="5dp">
<com.futo.platformplayer.views.behavior.NonScrollingTextView
android:id="@+id/videodetail_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@color/transparent"
android:fontFamily="@font/inter_light"
android:isScrollContainer="false"
android:maxLines="3"
android:textColor="@color/white"
android:textSize="11dp"
tools:text="@string/lorem_ipsum" />
<TextView
android:id="@+id/videodetail_description_view_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@color/transparent"
android:fontFamily="@font/inter_regular"
android:text="@string/click_to_read_more"
android:textAlignment="center"
android:textColor="#585656"
android:textSize="12dp" />
</LinearLayout>
<com.futo.platformplayer.views.MonetizationView
android:id="@+id/monetization"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.futo.platformplayer.views.videometa.UpNextView
android:id="@+id/up_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="@string/live_chat" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_change_bottom_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="14dp"
android:layout_marginTop="10dp"
android:layout_marginRight="14dp"
android:layout_marginBottom="10dp"
android:background="@drawable/background_videodetail_description"
android:orientation="horizontal">
<Button
android:id="@+id/button_polycentric"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:ellipsize="marquee"
android:lines="1"
android:padding="10dp"
android:text="Polycentric"
android:textColor="#fff"
android:textSize="10dp" />
<Button
android:id="@+id/button_platform"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:ellipsize="marquee"
android:lines="1"
android:padding="10dp"
android:text="Platform"
android:textColor="#fff"
android:textSize="10dp" />
<Button
android:id="@+id/button_recommended"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:ellipsize="marquee"
android:lines="1"
android:padding="10dp"
android:text="Recommended"
android:textColor="#fff"
android:textSize="10dp"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_recommended"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="gone"></LinearLayout>
<com.futo.platformplayer.views.comments.AddCommentView
android:id="@+id/add_comment_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="28dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="28dp"
android:layout_marginBottom="12dp" />
</LinearLayout>
<com.futo.platformplayer.views.segments.CommentsList
android:id="@+id/comments_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="400dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_top" />
</LinearLayout>
<com.futo.platformplayer.views.overlays.DescriptionOverlay
android:id="@+id/videodetail_container_description"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.futo.platformplayer.views.overlays.LiveChatOverlay
android:id="@+id/videodetail_container_livechat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.futo.platformplayer.views.overlays.WebviewOverlay
android:id="@+id/videodetail_container_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.futo.platformplayer.views.overlays.QueueEditorOverlay
android:id="@+id/videodetail_container_queue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.futo.platformplayer.views.overlays.RepliesOverlay
android:id="@+id/videodetail_container_replies"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.futo.platformplayer.views.overlays.SupportOverlay
android:id="@+id/videodetail_container_support"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
</LinearLayout>

View file

@ -16,7 +16,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="15dp"
@ -37,6 +37,6 @@
android:textSize="13sp"
android:textIsSelectable="true"
tools:text="@string/lorem_ipsum" />
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,8 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintBottom_toBottomOf="parent" />
<FrameLayout
android:id="@+id/short_view_loading_overlay"

View file

@ -560,6 +560,7 @@
<string name="enable_where_this_plugins_content_are_visible">Enable where this plugin\'s content are visible</string>
<string name="show_content_in_home_tab">Show content in home tab</string>
<string name="show_content_in_search_results">Show content in search results</string>
<string name="show_content_in_shorts_tab">Show content in shorts tab</string>
<string name="no_valid_url_provided">No valid URL provided..</string>
<string name="invalid_config_format">Invalid Config Format</string>
<string name="failed_to_fetch_configuration">Failed to fetch configuration</string>

View file

@ -41,4 +41,18 @@
<item name="cornerSizeBottomLeft">4dp</item>
<item name="cornerSizeBottomRight">4dp</item>
</style>
<!--this section here attempts to emulate the look of the default material 3 bottom sheet i got the values from the material 3 theme-->
<style name="Custom.BottomSheet.ShapeAppearance" parent="">
<item name="cornerFamily">?attr/shapeCornerFamily</item>
<item name="cornerSize">28dp</item>
</style>
<style name="Custom.BottomSheet.Modal.Style" parent="Widget.MaterialComponents.BottomSheet.Modal">
<item name="shapeAppearanceOverlay">@style/Custom.BottomSheet.ShapeAppearance</item>
<!-- force black background while grayjay only has a dark theme-->
<item name="backgroundTint">@color/black</item>
</style>
<style name="Custom.BottomSheetDialog.Theme" parent="@style/ThemeOverlay.Material3.BottomSheetDialog">
<item name="bottomSheetStyle">@style/Custom.BottomSheet.Modal.Style</item>
</style>
</resources>