mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
initial POC shorts tab
Changelog: added
This commit is contained in:
parent
c83a9924e2
commit
f63f9dd6db
17 changed files with 1235 additions and 15 deletions
|
@ -143,6 +143,10 @@ android {
|
|||
}
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.2"
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
|
@ -215,6 +219,7 @@ dependencies {
|
|||
//Database
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
annotationProcessor("androidx.room:room-compiler:2.6.1")
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling:1.7.8'
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
|
||||
|
@ -228,4 +233,10 @@ dependencies {
|
|||
testImplementation "org.mockito:mockito-core:5.4.0"
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
|
||||
//Compose
|
||||
def composeBom = platform('androidx.compose:compose-bom:2025.02.00')
|
||||
implementation composeBom
|
||||
androidTestImplementation composeBom
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.StrictMode
|
||||
|
@ -57,6 +55,7 @@ import com.futo.platformplayer.fragment.mainactivity.main.PlaylistSearchResultsF
|
|||
import com.futo.platformplayer.fragment.mainactivity.main.PlaylistsFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.PostDetailFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.RemotePlaylistFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.ShortsFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.SourcesFragment
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionGroupFragment
|
||||
|
@ -74,7 +73,6 @@ import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
|
|||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.ImportCache
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.receivers.MediaButtonReceiver
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateBackup
|
||||
|
@ -161,6 +159,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
lateinit var _fragMainRemotePlaylist: RemotePlaylistFragment;
|
||||
lateinit var _fragWatchlist: WatchLaterFragment;
|
||||
lateinit var _fragHistory: HistoryFragment;
|
||||
lateinit var _fragShorts: ShortsFragment;
|
||||
lateinit var _fragSourceDetail: SourceDetailFragment;
|
||||
lateinit var _fragDownloads: DownloadsFragment;
|
||||
lateinit var _fragImportSubscriptions: ImportSubscriptionsFragment;
|
||||
|
@ -315,6 +314,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
_fragPostDetail = PostDetailFragment.newInstance();
|
||||
_fragWatchlist = WatchLaterFragment.newInstance();
|
||||
_fragHistory = HistoryFragment.newInstance();
|
||||
_fragShorts = ShortsFragment.newInstance();
|
||||
_fragSourceDetail = SourceDetailFragment.newInstance();
|
||||
_fragDownloads = DownloadsFragment();
|
||||
_fragImportSubscriptions = ImportSubscriptionsFragment.newInstance();
|
||||
|
@ -1088,6 +1088,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
|
||||
if (segment.isMainView) {
|
||||
var transaction = supportFragmentManager.beginTransaction();
|
||||
transaction.setReorderingAllowed(true)
|
||||
if (segment.topBar != null) {
|
||||
if (segment.topBar != fragCurrent.topBar) {
|
||||
transaction = transaction
|
||||
|
@ -1188,6 +1189,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
PostDetailFragment::class -> _fragPostDetail as T;
|
||||
WatchLaterFragment::class -> _fragWatchlist as T;
|
||||
HistoryFragment::class -> _fragHistory as T;
|
||||
ShortsFragment::class -> _fragShorts as T;
|
||||
SourceDetailFragment::class -> _fragSourceDetail as T;
|
||||
DownloadsFragment::class -> _fragDownloads as T;
|
||||
ImportSubscriptionsFragment::class -> _fragImportSubscriptions as T;
|
||||
|
|
|
@ -386,16 +386,17 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
it.navigate<HomeFragment>()
|
||||
}
|
||||
}),
|
||||
ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate<SubscriptionsFeedFragment>() }),
|
||||
ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>() }),
|
||||
ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>() }),
|
||||
ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>() }),
|
||||
ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }),
|
||||
ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }),
|
||||
ButtonDefinition(1, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.shorts, canToggle = true, { it.currentMain is ShortsFragment }, { it.navigate<ShortsFragment>() }),
|
||||
ButtonDefinition(2, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate<SubscriptionsFeedFragment>() }),
|
||||
ButtonDefinition(3, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate<CreatorsFragment>() }),
|
||||
ButtonDefinition(4, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate<SourcesFragment>() }),
|
||||
ButtonDefinition(5, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>() }),
|
||||
ButtonDefinition(6, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }),
|
||||
ButtonDefinition(7, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }),
|
||||
ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>() }),
|
||||
ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate<SubscriptionGroupListFragment>() }),
|
||||
ButtonDefinition(10, R.drawable.ic_help_square, R.drawable.ic_help_square_fill, R.string.tutorials, canToggle = true, { it.currentMain is TutorialFragment }, { it.navigate<TutorialFragment>() }),
|
||||
ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings_filled, R.string.settings, canToggle = false, { false }, {
|
||||
ButtonDefinition(11, R.drawable.ic_settings, R.drawable.ic_settings_filled, R.string.settings, canToggle = false, { false }, {
|
||||
val c = it.context ?: return@ButtonDefinition;
|
||||
Logger.i(TAG, "settings preventPictureInPicture()");
|
||||
it.requireFragment<VideoDetailFragment>().preventPictureInPicture();
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.SoundEffectConstants
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ThumbUp
|
||||
import androidx.compose.material.icons.outlined.ThumbUp
|
||||
import androidx.compose.material.ripple.RippleAlpha
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconToggleButton
|
||||
import androidx.compose.material3.LocalRippleConfiguration
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RippleConfiguration
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.api.media.exceptions.ContentNotAvailableYetException
|
||||
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.downloads.VideoLocal
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
|
||||
import com.futo.platformplayer.exceptions.UnsupportedCastException
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StatePlatform
|
||||
import com.futo.platformplayer.states.StatePlugins
|
||||
import com.futo.platformplayer.views.video.FutoShortPlayer
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@UnstableApi
|
||||
class ShortView : ConstraintLayout {
|
||||
private var mainFragment: MainFragment? = null
|
||||
private val player: FutoShortPlayer
|
||||
private val overlayLoading: FrameLayout
|
||||
private val overlayLoadingSpinner: ImageView
|
||||
|
||||
private var url: String? = null
|
||||
private var video: IPlatformVideo? = null
|
||||
private var videoDetails: IPlatformVideoDetails? = null
|
||||
|
||||
private var playWhenReady = false
|
||||
|
||||
private var _lastVideoSource: IVideoSource? = null
|
||||
private var _lastAudioSource: IAudioSource? = null
|
||||
private var _lastSubtitleSource: ISubtitleSource? = null
|
||||
|
||||
private var loadVideoJob: Job? = null
|
||||
|
||||
private val bottomSheet: ModalBottomSheet = ModalBottomSheet()
|
||||
|
||||
// Required constructor for XML inflation
|
||||
constructor(context: Context) : super(context) {
|
||||
inflate(context, R.layout.view_short, this)
|
||||
|
||||
player = findViewById(R.id.short_player)
|
||||
overlayLoading = findViewById(R.id.short_view_loading_overlay)
|
||||
overlayLoadingSpinner = findViewById(R.id.short_view_loader)
|
||||
|
||||
setupComposeView()
|
||||
}
|
||||
|
||||
// Required constructor for XML inflation with attributes
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||
inflate(context, R.layout.view_short, this)
|
||||
|
||||
player = findViewById(R.id.short_player)
|
||||
overlayLoading = findViewById(R.id.short_view_loading_overlay)
|
||||
overlayLoadingSpinner = findViewById(R.id.short_view_loader)
|
||||
|
||||
setupComposeView()
|
||||
}
|
||||
|
||||
// Required constructor for XML inflation with attributes and style
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
inflate(context, R.layout.view_short, this)
|
||||
|
||||
player = findViewById(R.id.short_player)
|
||||
overlayLoading = findViewById(R.id.short_view_loading_overlay)
|
||||
overlayLoadingSpinner = findViewById(R.id.short_view_loader)
|
||||
|
||||
setupComposeView()
|
||||
}
|
||||
|
||||
constructor(inflater: LayoutInflater, fragment: MainFragment) : super(inflater.context) {
|
||||
this.mainFragment = fragment
|
||||
|
||||
inflater.inflate(R.layout.view_short, this, true)
|
||||
player = findViewById(R.id.short_player)
|
||||
overlayLoading = findViewById(R.id.short_view_loading_overlay)
|
||||
overlayLoadingSpinner = findViewById(R.id.short_view_loader)
|
||||
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupComposeView () {
|
||||
val composeView: ComposeView = findViewById(R.id.compose_view_test_button)
|
||||
composeView.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
// In Compose world
|
||||
MaterialTheme {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
|
||||
val tint = Color.White
|
||||
|
||||
val alpha = 0.2f
|
||||
val rippleConfiguration =
|
||||
RippleConfiguration(color = tint, rippleAlpha = RippleAlpha(alpha, alpha, alpha, alpha))
|
||||
|
||||
val view = LocalView.current
|
||||
|
||||
CompositionLocalProvider(LocalRippleConfiguration provides rippleConfiguration) {
|
||||
IconToggleButton(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
},
|
||||
) {
|
||||
if (checked) {
|
||||
Icon(
|
||||
Icons.Filled.ThumbUp, contentDescription = "Liked", tint = tint,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
Icons.Outlined.ThumbUp, contentDescription = "Not Liked", tint = tint,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setMainFragment(fragment: MainFragment) {
|
||||
this.mainFragment = fragment
|
||||
}
|
||||
|
||||
fun setVideo(url: String) {
|
||||
if (url == this.url) {
|
||||
return
|
||||
}
|
||||
|
||||
loadVideo(url)
|
||||
}
|
||||
|
||||
fun setVideo(video: IPlatformVideo) {
|
||||
if (url == video.url) {
|
||||
return
|
||||
}
|
||||
this.video = video
|
||||
|
||||
loadVideo(video.url)
|
||||
}
|
||||
|
||||
fun setVideo(videoDetails: IPlatformVideoDetails) {
|
||||
if (url == videoDetails.url) {
|
||||
return
|
||||
}
|
||||
|
||||
this.videoDetails = videoDetails
|
||||
}
|
||||
|
||||
fun play() {
|
||||
player.attach()
|
||||
playVideo()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
playWhenReady = false
|
||||
|
||||
player.clear()
|
||||
player.detach()
|
||||
}
|
||||
|
||||
fun detach() {
|
||||
loadVideoJob?.cancel()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVideo(url: String) {
|
||||
loadVideoJob?.cancel()
|
||||
|
||||
loadVideoJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
setLoading(true)
|
||||
_lastVideoSource = null
|
||||
_lastAudioSource = null
|
||||
_lastSubtitleSource = null
|
||||
|
||||
val result = try {
|
||||
withContext(StateApp.instance.scope.coroutineContext) {
|
||||
StatePlatform.instance.getContentDetails(url).await()
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
return@launch
|
||||
} catch (e: NoPlatformClientException) {
|
||||
Logger.w(TAG, "exception<NoPlatformClientException>", e)
|
||||
|
||||
UIDialogs.showDialog(
|
||||
context, R.drawable.ic_sources, "No source enabled to support this video\n(${url})", null, null, 0, UIDialogs.Action(
|
||||
"Close", { }, UIDialogs.ActionStyle.PRIMARY
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
} catch (e: ScriptLoginRequiredException) {
|
||||
Logger.w(TAG, "exception<ScriptLoginRequiredException>", e)
|
||||
UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", e.message, null, 0, UIDialogs.Action("Cancel", {}), UIDialogs.Action("Login", {
|
||||
val id = e.config.let { if (it is SourcePluginConfig) it.id else null }
|
||||
val didLogin =
|
||||
if (id == null) false else StatePlugins.instance.loginPlugin(context, id) {
|
||||
loadVideo(url)
|
||||
}
|
||||
if (!didLogin) UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login")
|
||||
}, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
return@launch
|
||||
} catch (e: ContentNotAvailableYetException) {
|
||||
Logger.w(TAG, "exception<ContentNotAvailableYetException>", e)
|
||||
UIDialogs.showSingleButtonDialog(context, R.drawable.ic_schedule, "Video is available in ${e.availableWhen}.", "Close") { }
|
||||
return@launch
|
||||
} catch (e: ScriptImplementationException) {
|
||||
Logger.w(TAG, "exception<ScriptImplementationException>", e)
|
||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptimplementationexception), e, { loadVideo(url) }, null, mainFragment)
|
||||
return@launch
|
||||
} catch (e: ScriptAgeException) {
|
||||
Logger.w(TAG, "exception<ScriptAgeException>", e)
|
||||
UIDialogs.showDialog(
|
||||
context, R.drawable.ic_lock, "Age restricted video", e.message, null, 0, UIDialogs.Action("Close", { }, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
return@launch
|
||||
} catch (e: ScriptUnavailableException) {
|
||||
Logger.w(TAG, "exception<ScriptUnavailableException>", e)
|
||||
if (video?.datetime == null || video?.datetime!! < OffsetDateTime.now()
|
||||
.minusHours(1)
|
||||
) {
|
||||
UIDialogs.showDialog(
|
||||
context, R.drawable.ic_lock, context.getString(R.string.unavailable_video), context.getString(R.string.this_video_is_unavailable), null, 0, UIDialogs.Action(context.getString(R.string.close), { }, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
}
|
||||
|
||||
video?.let { StatePlatform.instance.clearContentDetailCache(it.url) }
|
||||
return@launch
|
||||
} catch (e: ScriptException) {
|
||||
Logger.w(TAG, "exception<ScriptException>", e)
|
||||
|
||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptexception), e, { loadVideo(url) }, null, mainFragment)
|
||||
return@launch
|
||||
} catch (e: Throwable) {
|
||||
Logger.w(ChannelFragment.TAG, "Failed to load video.", e)
|
||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), e, { loadVideo(url) }, null, mainFragment)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (result !is IPlatformVideoDetails) {
|
||||
Logger.w(
|
||||
TAG, "Wrong content type", IllegalStateException("Expected media content, found ${result.contentType}")
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
// if it's been canceled then don't set the video details
|
||||
if (!isActive) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
videoDetails = result
|
||||
video = result
|
||||
|
||||
setLoading(false)
|
||||
|
||||
if (playWhenReady) playVideo()
|
||||
}
|
||||
}
|
||||
|
||||
private fun playVideo(resumePositionMs: Long = 0) {
|
||||
val video = videoDetails
|
||||
|
||||
if (video === null) {
|
||||
playWhenReady = true
|
||||
return
|
||||
}
|
||||
|
||||
bottomSheet.show(mainFragment!!.childFragmentManager, ModalBottomSheet.TAG)
|
||||
|
||||
try {
|
||||
val videoSource = _lastVideoSource
|
||||
?: player.getPreferredVideoSource(video, Settings.instance.playback.getCurrentPreferredQualityPixelCount())
|
||||
val audioSource = _lastAudioSource
|
||||
?: player.getPreferredAudioSource(video, Settings.instance.playback.getPrimaryLanguage(context))
|
||||
val subtitleSource = _lastSubtitleSource
|
||||
?: (if (video is VideoLocal) video.subtitlesSources.firstOrNull() else null)
|
||||
Logger.i(TAG, "loadCurrentVideo(videoSource=$videoSource, audioSource=$audioSource, subtitleSource=$subtitleSource, resumePositionMs=$resumePositionMs)")
|
||||
|
||||
if (videoSource == null && audioSource == null) {
|
||||
UIDialogs.showDialog(
|
||||
context, R.drawable.ic_lock, context.getString(R.string.unavailable_video), context.getString(R.string.this_video_is_unavailable), null, 0, UIDialogs.Action(context.getString(R.string.close), { }, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
StatePlatform.instance.clearContentDetailCache(video.url)
|
||||
return
|
||||
}
|
||||
|
||||
val thumbnail = video.thumbnails.getHQThumbnail()
|
||||
if (videoSource == null && !thumbnail.isNullOrBlank()) Glide.with(context).asBitmap()
|
||||
.load(thumbnail).into(object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
player.setArtwork(BitmapDrawable(resources, resource))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
player.setArtwork(null)
|
||||
}
|
||||
})
|
||||
else player.setArtwork(null)
|
||||
player.setSource(videoSource, audioSource, play = true, keepSubtitles = false, resume = resumePositionMs > 0)
|
||||
if (subtitleSource != null) player.swapSubtitles(mainFragment!!.lifecycleScope, subtitleSource)
|
||||
player.seekTo(resumePositionMs)
|
||||
|
||||
_lastVideoSource = videoSource
|
||||
_lastAudioSource = audioSource
|
||||
_lastSubtitleSource = subtitleSource
|
||||
} catch (ex: UnsupportedCastException) {
|
||||
Logger.e(TAG, "Failed to load cast media", ex)
|
||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.unsupported_cast_format), ex)
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to load media", ex)
|
||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_load_media), ex)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "VideoDetailView"
|
||||
}
|
||||
|
||||
class ModalBottomSheet : BottomSheetDialogFragment() {
|
||||
override fun onCreateDialog(
|
||||
savedInstanceState: Bundle?,
|
||||
): Dialog {
|
||||
val bottomSheetDialog = BottomSheetDialog(
|
||||
requireContext()
|
||||
)
|
||||
bottomSheetDialog.setContentView(R.layout.modal_comments)
|
||||
|
||||
val composeView = bottomSheetDialog.findViewById<ComposeView>(R.id.compose_view)
|
||||
|
||||
composeView?.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
// In Compose world
|
||||
MaterialTheme {
|
||||
val view = LocalView.current
|
||||
IconButton(onClick = {
|
||||
view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Outlined.ThumbUp, contentDescription = "Close Bottom Sheet"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bottomSheetDialog
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "ModalBottomSheet"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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
|
||||
|
||||
@UnstableApi
|
||||
class ShortsFragment : MainFragment() {
|
||||
override val isMainView: Boolean = true
|
||||
override val isTab: Boolean = true
|
||||
override val hasBottomBar: Boolean get() = true
|
||||
|
||||
private var previousShownView: ShortView? = null
|
||||
|
||||
private lateinit var viewPager: ViewPager2
|
||||
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"
|
||||
)
|
||||
|
||||
override fun onCreateMainView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_shorts, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewPager = view.findViewById(R.id.viewPager)
|
||||
|
||||
customViewAdapter = CustomViewAdapter(urls, layoutInflater, this)
|
||||
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) {
|
||||
previousShownView?.stop()
|
||||
|
||||
val focusedView =
|
||||
((viewPager[0] as RecyclerView).findViewHolderForAdapterPosition(position) as CustomViewHolder).shortView
|
||||
focusedView.play()
|
||||
|
||||
|
||||
previousShownView = focusedView
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
previousShownView?.stop()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ShortsFragment"
|
||||
|
||||
fun newInstance() = ShortsFragment()
|
||||
}
|
||||
|
||||
class CustomViewAdapter(
|
||||
private val urls: List<String>, private val inflater: LayoutInflater, private val fragment: MainFragment
|
||||
) : RecyclerView.Adapter<CustomViewHolder>() {
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
|
||||
val shortView = ShortView(inflater, fragment)
|
||||
return CustomViewHolder(shortView)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
|
||||
holder.shortView.setVideo(urls[position])
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onViewRecycled(holder: CustomViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
holder.shortView.detach()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = urls.size
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class CustomViewHolder(val shortView: ShortView) : RecyclerView.ViewHolder(shortView)
|
||||
}
|
|
@ -38,6 +38,7 @@ class StatePlayer {
|
|||
//Players
|
||||
private var _exoplayer : PlayerManager? = null;
|
||||
private var _thumbnailExoPlayer : PlayerManager? = null;
|
||||
private var _shortExoPlayer: PlayerManager? = null
|
||||
|
||||
//Video Status
|
||||
var rotationLock: Boolean = false
|
||||
|
@ -633,6 +634,13 @@ class StatePlayer {
|
|||
}
|
||||
return _thumbnailExoPlayer!!;
|
||||
}
|
||||
fun getShortPlayerOrCreate(context: Context) : PlayerManager {
|
||||
if(_shortExoPlayer == null) {
|
||||
val player = createExoPlayer(context);
|
||||
_shortExoPlayer = PlayerManager(player);
|
||||
}
|
||||
return _shortExoPlayer!!;
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun createExoPlayer(context : Context): ExoPlayer {
|
||||
|
@ -656,10 +664,13 @@ class StatePlayer {
|
|||
fun dispose(){
|
||||
val player = _exoplayer;
|
||||
val thumbPlayer = _thumbnailExoPlayer;
|
||||
val shortPlayer = _shortExoPlayer
|
||||
_exoplayer = null;
|
||||
_thumbnailExoPlayer = null;
|
||||
_shortExoPlayer = null
|
||||
player?.release();
|
||||
thumbPlayer?.release();
|
||||
shortPlayer?.release()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
package com.futo.platformplayer.views.video
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.animation.LinearInterpolator
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.DefaultTimeBar
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.TimeBar
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
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.states.StatePlayer
|
||||
|
||||
@UnstableApi
|
||||
class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
|
||||
FutoVideoPlayerBase(PLAYER_STATE_NAME, context, attrs) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FutoShortVideoPlayer"
|
||||
private const val PLAYER_STATE_NAME: String = "ShortPlayer"
|
||||
}
|
||||
|
||||
private var playerAttached = false
|
||||
|
||||
private val videoView: PlayerView
|
||||
private val progressBar: DefaultTimeBar
|
||||
|
||||
private val loadArtwork = object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
setArtwork(BitmapDrawable(resources, resource))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
setArtwork(null)
|
||||
}
|
||||
}
|
||||
|
||||
private val player = StatePlayer.instance.getShortPlayerOrCreate(context)
|
||||
|
||||
private var progressAnimator: ValueAnimator = createProgressBarAnimator()
|
||||
|
||||
private var playerEventListener = object : Player.Listener {
|
||||
override fun onEvents(player: Player, events: Player.Events) {
|
||||
if (events.containsAny(
|
||||
Player.EVENT_POSITION_DISCONTINUITY, Player.EVENT_IS_PLAYING_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED
|
||||
)
|
||||
) {
|
||||
if (player.duration >= 0) {
|
||||
progressAnimator.duration = player.duration
|
||||
setProgressBarDuration(player.duration)
|
||||
progressAnimator.currentPlayTime = player.currentPosition
|
||||
}
|
||||
|
||||
if (player.isPlaying) {
|
||||
if (!progressAnimator.isStarted) {
|
||||
progressAnimator.start()
|
||||
}
|
||||
} else {
|
||||
if (progressAnimator.isRunning) {
|
||||
progressAnimator.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_short_player, this, true)
|
||||
videoView = findViewById(R.id.video_player)
|
||||
progressBar = findViewById(R.id.video_player_progress_bar)
|
||||
|
||||
progressBar.addListener(object : TimeBar.OnScrubListener {
|
||||
override fun onScrubStart(timeBar: TimeBar, position: Long) {
|
||||
if (progressAnimator.isRunning) {
|
||||
progressAnimator.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrubMove(timeBar: TimeBar, position: Long) {}
|
||||
|
||||
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
|
||||
if (canceled) {
|
||||
progressAnimator.currentPlayTime = player.player.currentPosition
|
||||
progressAnimator.start()
|
||||
return
|
||||
}
|
||||
|
||||
// the progress bar should never be available to the user without the player being attached to this view
|
||||
assert(playerAttached)
|
||||
seekTo(position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun createProgressBarAnimator(): ValueAnimator {
|
||||
return ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
interpolator = LinearInterpolator()
|
||||
|
||||
addUpdateListener { animation ->
|
||||
val progress = animation.animatedValue as Float
|
||||
val duration = animation.duration
|
||||
progressBar.setPosition((progress * duration).toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setProgressBarDuration(duration: Long) {
|
||||
progressBar.setDuration(duration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches this short player instance to the exo player instance for shorts
|
||||
*/
|
||||
fun attach() {
|
||||
// connect the exo player for shorts to the view for this instance
|
||||
player.attach(videoView, PLAYER_STATE_NAME)
|
||||
|
||||
// direct the base player what exo player instance to use
|
||||
changePlayer(player)
|
||||
|
||||
playerAttached = true
|
||||
|
||||
player.player.addListener(playerEventListener)
|
||||
}
|
||||
|
||||
fun detach() {
|
||||
playerAttached = false
|
||||
player.player.removeListener(playerEventListener)
|
||||
player.detach()
|
||||
}
|
||||
|
||||
fun setPreview(video: IPlatformVideoDetails) {
|
||||
if (video.live != null) {
|
||||
setSource(video.live, null, play = true, keepSubtitles = false)
|
||||
} else {
|
||||
val videoSource =
|
||||
VideoHelper.selectBestVideoSource(video.video, Settings.instance.playback.getPreferredPreviewQualityPixelCount(), PREFERED_VIDEO_CONTAINERS)
|
||||
val audioSource =
|
||||
VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, Settings.instance.playback.getPrimaryLanguage(context))
|
||||
if (videoSource == null && audioSource != null) {
|
||||
val thumbnail = video.thumbnails.getHQThumbnail()
|
||||
if (!thumbnail.isNullOrBlank()) {
|
||||
Glide.with(videoView).asBitmap().load(thumbnail).into(loadArtwork)
|
||||
} else {
|
||||
Glide.with(videoView).clear(loadArtwork)
|
||||
setArtwork(null)
|
||||
}
|
||||
} else {
|
||||
Glide.with(videoView).clear(loadArtwork)
|
||||
}
|
||||
|
||||
setSource(videoSource, audioSource, play = true, keepSubtitles = false)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun setArtwork(drawable: Drawable?) {
|
||||
if (drawable != null) {
|
||||
videoView.defaultArtwork = drawable
|
||||
videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_FILL
|
||||
} else {
|
||||
videoView.defaultArtwork = null
|
||||
videoView.artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_OFF
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import android.net.Uri
|
|||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.media3.common.C
|
||||
|
@ -68,7 +69,7 @@ import java.io.ByteArrayInputStream
|
|||
import java.io.File
|
||||
import kotlin.math.abs
|
||||
|
||||
abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||
abstract class FutoVideoPlayerBase : ConstraintLayout {
|
||||
private val TAG = "FutoVideoPlayerBase"
|
||||
|
||||
private val TEMP_DIRECTORY = StateApp.instance.getTempDirectory();
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.MainActivity"
|
||||
android:background="@color/black"
|
||||
|
@ -96,4 +97,4 @@
|
|||
app:layout_constraintRight_toRightOf="@id/fragment_main"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fragment_main" />
|
||||
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/bottom_bar_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_height="48dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
6
app/src/main/res/layout/fragment_shorts.xml
Normal file
6
app/src/main/res/layout/fragment_shorts.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
231
app/src/main/res/layout/modal_comments.xml
Normal file
231
app/src/main/res/layout/modal_comments.xml
Normal file
|
@ -0,0 +1,231 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Drag handle for accessibility -->
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
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_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
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" />
|
||||
|
||||
<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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</LinearLayout>
|
43
app/src/main/res/layout/view_short.xml
Normal file
43
app/src/main/res/layout/view_short.xml
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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:background="@drawable/bottom_menu_border">
|
||||
|
||||
<com.futo.platformplayer.views.video.FutoShortPlayer
|
||||
android:id="@+id/short_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_view_test_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="1dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<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>
|
||||
</merge>
|
31
app/src/main/res/layout/view_short_player.xml
Normal file
31
app/src/main/res/layout/view_short_player.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/video_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/black"
|
||||
app:default_artwork="@drawable/placeholder_video_thumbnail"
|
||||
app:layout_constraintBottom_toTopOf="@id/video_player_progress_bar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:resize_mode="fit"
|
||||
app:show_buffering="when_playing"
|
||||
app:use_artwork="true"
|
||||
app:use_controller="false" />
|
||||
|
||||
<androidx.media3.ui.DefaultTimeBar
|
||||
android:id="@+id/video_player_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="6dp"
|
||||
app:bar_height="6dp"
|
||||
app:buffered_color="#DDEEEEEE"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/video_player"
|
||||
app:played_color="@color/colorPrimary"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_dragged_size="0dp"
|
||||
app:scrubber_enabled_size="0dp"
|
||||
app:unplayed_color="#55EEEEEE" />
|
||||
</merge>
|
|
@ -38,4 +38,147 @@
|
|||
<color name="overlay">#B3000000</color>
|
||||
<color name="text_color_tinted">#ACACAC</color>
|
||||
<color name="pastel_red">#C25353</color>
|
||||
|
||||
<!--material 3 colors-->
|
||||
<color name="md_theme_primary">#8F4C38</color>
|
||||
<color name="md_theme_onPrimary">#FFFFFF</color>
|
||||
<color name="md_theme_primaryContainer">#FFDBD1</color>
|
||||
<color name="md_theme_onPrimaryContainer">#723523</color>
|
||||
<color name="md_theme_secondary">#77574E</color>
|
||||
<color name="md_theme_onSecondary">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryContainer">#FFDBD1</color>
|
||||
<color name="md_theme_onSecondaryContainer">#5D4037</color>
|
||||
<color name="md_theme_tertiary">#6C5D2F</color>
|
||||
<color name="md_theme_onTertiary">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryContainer">#F5E1A7</color>
|
||||
<color name="md_theme_onTertiaryContainer">#534619</color>
|
||||
<color name="md_theme_error">#BA1A1A</color>
|
||||
<color name="md_theme_onError">#FFFFFF</color>
|
||||
<color name="md_theme_errorContainer">#FFDAD6</color>
|
||||
<color name="md_theme_onErrorContainer">#93000A</color>
|
||||
<color name="md_theme_background">#FFF8F6</color>
|
||||
<color name="md_theme_onBackground">#231917</color>
|
||||
<color name="md_theme_surface">#FFF8F6</color>
|
||||
<color name="md_theme_onSurface">#231917</color>
|
||||
<color name="md_theme_surfaceVariant">#F5DED8</color>
|
||||
<color name="md_theme_onSurfaceVariant">#53433F</color>
|
||||
<color name="md_theme_outline">#85736E</color>
|
||||
<color name="md_theme_outlineVariant">#D8C2BC</color>
|
||||
<color name="md_theme_scrim">#000000</color>
|
||||
<color name="md_theme_inverseSurface">#392E2B</color>
|
||||
<color name="md_theme_inverseOnSurface">#FFEDE8</color>
|
||||
<color name="md_theme_inversePrimary">#FFB5A0</color>
|
||||
<color name="md_theme_primaryFixed">#FFDBD1</color>
|
||||
<color name="md_theme_onPrimaryFixed">#3A0B01</color>
|
||||
<color name="md_theme_primaryFixedDim">#FFB5A0</color>
|
||||
<color name="md_theme_onPrimaryFixedVariant">#723523</color>
|
||||
<color name="md_theme_secondaryFixed">#FFDBD1</color>
|
||||
<color name="md_theme_onSecondaryFixed">#2C150F</color>
|
||||
<color name="md_theme_secondaryFixedDim">#E7BDB2</color>
|
||||
<color name="md_theme_onSecondaryFixedVariant">#5D4037</color>
|
||||
<color name="md_theme_tertiaryFixed">#F5E1A7</color>
|
||||
<color name="md_theme_onTertiaryFixed">#231B00</color>
|
||||
<color name="md_theme_tertiaryFixedDim">#D8C58D</color>
|
||||
<color name="md_theme_onTertiaryFixedVariant">#534619</color>
|
||||
<color name="md_theme_surfaceDim">#E8D6D2</color>
|
||||
<color name="md_theme_surfaceBright">#FFF8F6</color>
|
||||
<color name="md_theme_surfaceContainerLowest">#FFFFFF</color>
|
||||
<color name="md_theme_surfaceContainerLow">#FFF1ED</color>
|
||||
<color name="md_theme_surfaceContainer">#FCEAE5</color>
|
||||
<color name="md_theme_surfaceContainerHigh">#F7E4E0</color>
|
||||
<color name="md_theme_surfaceContainerHighest">#F1DFDA</color>
|
||||
<color name="md_theme_primary_mediumContrast">#5D2514</color>
|
||||
<color name="md_theme_onPrimary_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_primaryContainer_mediumContrast">#A15A45</color>
|
||||
<color name="md_theme_onPrimaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondary_mediumContrast">#4B2F28</color>
|
||||
<color name="md_theme_onSecondary_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryContainer_mediumContrast">#87655C</color>
|
||||
<color name="md_theme_onSecondaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiary_mediumContrast">#41350A</color>
|
||||
<color name="md_theme_onTertiary_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryContainer_mediumContrast">#7B6C3C</color>
|
||||
<color name="md_theme_onTertiaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_error_mediumContrast">#740006</color>
|
||||
<color name="md_theme_onError_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_errorContainer_mediumContrast">#CF2C27</color>
|
||||
<color name="md_theme_onErrorContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_background_mediumContrast">#FFF8F6</color>
|
||||
<color name="md_theme_onBackground_mediumContrast">#231917</color>
|
||||
<color name="md_theme_surface_mediumContrast">#FFF8F6</color>
|
||||
<color name="md_theme_onSurface_mediumContrast">#180F0D</color>
|
||||
<color name="md_theme_surfaceVariant_mediumContrast">#F5DED8</color>
|
||||
<color name="md_theme_onSurfaceVariant_mediumContrast">#41332F</color>
|
||||
<color name="md_theme_outline_mediumContrast">#5F4F4A</color>
|
||||
<color name="md_theme_outlineVariant_mediumContrast">#7B6964</color>
|
||||
<color name="md_theme_scrim_mediumContrast">#000000</color>
|
||||
<color name="md_theme_inverseSurface_mediumContrast">#392E2B</color>
|
||||
<color name="md_theme_inverseOnSurface_mediumContrast">#FFEDE8</color>
|
||||
<color name="md_theme_inversePrimary_mediumContrast">#FFB5A0</color>
|
||||
<color name="md_theme_primaryFixed_mediumContrast">#A15A45</color>
|
||||
<color name="md_theme_onPrimaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_primaryFixedDim_mediumContrast">#84422F</color>
|
||||
<color name="md_theme_onPrimaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryFixed_mediumContrast">#87655C</color>
|
||||
<color name="md_theme_onSecondaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryFixedDim_mediumContrast">#6D4D45</color>
|
||||
<color name="md_theme_onSecondaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryFixed_mediumContrast">#7B6C3C</color>
|
||||
<color name="md_theme_onTertiaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryFixedDim_mediumContrast">#615426</color>
|
||||
<color name="md_theme_onTertiaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_surfaceDim_mediumContrast">#D4C3BE</color>
|
||||
<color name="md_theme_surfaceBright_mediumContrast">#FFF8F6</color>
|
||||
<color name="md_theme_surfaceContainerLowest_mediumContrast">#FFFFFF</color>
|
||||
<color name="md_theme_surfaceContainerLow_mediumContrast">#FFF1ED</color>
|
||||
<color name="md_theme_surfaceContainer_mediumContrast">#F7E4E0</color>
|
||||
<color name="md_theme_surfaceContainerHigh_mediumContrast">#EBD9D4</color>
|
||||
<color name="md_theme_surfaceContainerHighest_mediumContrast">#DFCEC9</color>
|
||||
<color name="md_theme_primary_highContrast">#501B0B</color>
|
||||
<color name="md_theme_onPrimary_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_primaryContainer_highContrast">#753725</color>
|
||||
<color name="md_theme_onPrimaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondary_highContrast">#3F261E</color>
|
||||
<color name="md_theme_onSecondary_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryContainer_highContrast">#60423A</color>
|
||||
<color name="md_theme_onSecondaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiary_highContrast">#362B02</color>
|
||||
<color name="md_theme_onTertiary_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryContainer_highContrast">#55481C</color>
|
||||
<color name="md_theme_onTertiaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_error_highContrast">#600004</color>
|
||||
<color name="md_theme_onError_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_errorContainer_highContrast">#98000A</color>
|
||||
<color name="md_theme_onErrorContainer_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_background_highContrast">#FFF8F6</color>
|
||||
<color name="md_theme_onBackground_highContrast">#231917</color>
|
||||
<color name="md_theme_surface_highContrast">#FFF8F6</color>
|
||||
<color name="md_theme_onSurface_highContrast">#000000</color>
|
||||
<color name="md_theme_surfaceVariant_highContrast">#F5DED8</color>
|
||||
<color name="md_theme_onSurfaceVariant_highContrast">#000000</color>
|
||||
<color name="md_theme_outline_highContrast">#372925</color>
|
||||
<color name="md_theme_outlineVariant_highContrast">#554641</color>
|
||||
<color name="md_theme_scrim_highContrast">#000000</color>
|
||||
<color name="md_theme_inverseSurface_highContrast">#392E2B</color>
|
||||
<color name="md_theme_inverseOnSurface_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_inversePrimary_highContrast">#FFB5A0</color>
|
||||
<color name="md_theme_primaryFixed_highContrast">#753725</color>
|
||||
<color name="md_theme_onPrimaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_primaryFixedDim_highContrast">#592111</color>
|
||||
<color name="md_theme_onPrimaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryFixed_highContrast">#60423A</color>
|
||||
<color name="md_theme_onSecondaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_secondaryFixedDim_highContrast">#472C24</color>
|
||||
<color name="md_theme_onSecondaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryFixed_highContrast">#55481C</color>
|
||||
<color name="md_theme_onTertiaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_tertiaryFixedDim_highContrast">#3D3206</color>
|
||||
<color name="md_theme_onTertiaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_surfaceDim_highContrast">#C6B5B1</color>
|
||||
<color name="md_theme_surfaceBright_highContrast">#FFF8F6</color>
|
||||
<color name="md_theme_surfaceContainerLowest_highContrast">#FFFFFF</color>
|
||||
<color name="md_theme_surfaceContainerLow_highContrast">#FFEDE8</color>
|
||||
<color name="md_theme_surfaceContainer_highContrast">#F1DFDA</color>
|
||||
<color name="md_theme_surfaceContainerHigh_highContrast">#E2D1CC</color>
|
||||
<color name="md_theme_surfaceContainerHighest_highContrast">#D4C3BE</color>
|
||||
</resources>
|
|
@ -25,6 +25,7 @@
|
|||
<string name="failed_to_retrieve_data_are_you_connected">Failed to retrieve data, are you connected?</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="history">History</string>
|
||||
<string name="shorts">Shorts</string>
|
||||
<string name="sources">Sources</string>
|
||||
<string name="buy">Buy</string>
|
||||
<string name="faq">FAQ</string>
|
||||
|
|
|
@ -49,6 +49,42 @@
|
|||
<item name="android:listViewStyle">@style/Theme.FutoVideo.ListView</item>
|
||||
<item name="android:textViewStyle">@style/Theme.FutoVideo.TextView</item>
|
||||
<item name="android:checkedTextViewStyle">@style/Theme.FutoVideo.CheckedTextView</item>
|
||||
|
||||
<!-- Material3 attributes (needed if parent="Theme.MaterialComponents"). -->
|
||||
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary</item>
|
||||
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
|
||||
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
|
||||
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
|
||||
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
|
||||
<item name="colorTertiary">@color/md_theme_tertiary</item>
|
||||
<item name="colorOnTertiary">@color/md_theme_onTertiary</item>
|
||||
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer</item>
|
||||
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer</item>
|
||||
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant</item>
|
||||
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface</item>
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface</item>
|
||||
<item name="colorOutline">@color/md_theme_outline</item>
|
||||
<item name="colorErrorContainer">@color/md_theme_errorContainer</item>
|
||||
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer</item>
|
||||
<item name="textAppearanceDisplayLarge">@style/TextAppearance.Material3.DisplayLarge</item>
|
||||
<item name="textAppearanceDisplayMedium">@style/TextAppearance.Material3.DisplayMedium</item>
|
||||
<item name="textAppearanceDisplaySmall">@style/TextAppearance.Material3.DisplaySmall</item>
|
||||
<item name="textAppearanceHeadlineLarge">@style/TextAppearance.Material3.HeadlineLarge</item>
|
||||
<item name="textAppearanceHeadlineMedium">@style/TextAppearance.Material3.HeadlineMedium</item>
|
||||
<item name="textAppearanceHeadlineSmall">@style/TextAppearance.Material3.HeadlineSmall</item>
|
||||
<item name="textAppearanceTitleLarge">@style/TextAppearance.Material3.TitleLarge</item>
|
||||
<item name="textAppearanceTitleMedium">@style/TextAppearance.Material3.TitleMedium</item>
|
||||
<item name="textAppearanceTitleSmall">@style/TextAppearance.Material3.TitleSmall</item>
|
||||
<item name="textAppearanceBodyLarge">@style/TextAppearance.Material3.BodyLarge</item>
|
||||
<item name="textAppearanceBodyMedium">@style/TextAppearance.Material3.BodyMedium</item>
|
||||
<item name="textAppearanceBodySmall">@style/TextAppearance.Material3.BodySmall</item>
|
||||
<item name="textAppearanceLabelLarge">@style/TextAppearance.Material3.LabelLarge</item>
|
||||
<item name="textAppearanceLabelMedium">@style/TextAppearance.Material3.LabelMedium</item>
|
||||
<item name="textAppearanceLabelSmall">@style/TextAppearance.Material3.LabelSmall</item>
|
||||
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.Material3.SmallComponent</item>
|
||||
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.Material3.MediumComponent</item>
|
||||
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.Material3.LargeComponent</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.FutoVideo.NoActionBar" parent="Theme.FutoVideo">
|
||||
|
|
Loading…
Add table
Reference in a new issue