0.3.03
This commit is contained in:
@@ -109,6 +109,7 @@ data class RemoteFeatures(
|
||||
data class RemotePresentation(
|
||||
val navigationRailExpandedWidthDp: Int = 184,
|
||||
val navigationContentShiftDp: Int = 112,
|
||||
val fontFamily: String = "system",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.service.dreams.DreamService
|
||||
import android.view.KeyEvent
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
@@ -18,6 +20,7 @@ import androidx.savedstate.SavedStateRegistry
|
||||
import androidx.savedstate.SavedStateRegistryController
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.ponzischeme89.memby.ServiceLocator
|
||||
import com.ponzischeme89.memby.ui.player.PlayerActivity
|
||||
import com.ponzischeme89.memby.data.model.BaseItem
|
||||
import com.ponzischeme89.memby.data.model.TrailerPlaybackRequest
|
||||
@@ -60,7 +63,8 @@ class MembyDreamService : DreamService() {
|
||||
setViewTreeSavedStateRegistryOwner(lifecycleOwner)
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MembyTheme {
|
||||
val remoteConfig by ServiceLocator.remoteConfig.activeFlow.collectAsState()
|
||||
MembyTheme(fontFamilyName = remoteConfig.presentation.fontFamily) {
|
||||
ScreensaverContent(
|
||||
onPlay = ::launchTrailer,
|
||||
onExit = { finish() },
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ponzischeme89.memby.ui
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
|
||||
/** A restrained pull-back that gives large hero artwork some life without repeating. */
|
||||
@Composable
|
||||
internal fun Modifier.cinematicBackdropPullBack(
|
||||
artworkKey: Any?,
|
||||
alpha: () -> Float = { 1f },
|
||||
): Modifier {
|
||||
val scale = remember(artworkKey) { Animatable(CinematicBackdropInitialScale) }
|
||||
LaunchedEffect(artworkKey) {
|
||||
scale.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = CinematicBackdropPullBackMs,
|
||||
easing = LinearOutSlowInEasing,
|
||||
),
|
||||
)
|
||||
}
|
||||
return graphicsLayer {
|
||||
scaleX = scale.value
|
||||
scaleY = scale.value
|
||||
this.alpha = alpha().coerceIn(0f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
private const val CinematicBackdropInitialScale = 1.08f
|
||||
private const val CinematicBackdropPullBackMs = 10_000
|
||||
@@ -114,6 +114,7 @@ import com.ponzischeme89.memby.ui.theme.MembyPanelCorner
|
||||
import com.ponzischeme89.memby.ui.theme.MembyQuietText
|
||||
import com.ponzischeme89.memby.ui.theme.MembySurface
|
||||
import com.ponzischeme89.memby.ui.theme.MembySurfaceRaised
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTrialTypography
|
||||
import com.ponzischeme89.memby.ui.theme.ValueSeparator
|
||||
|
||||
// The detail page's names for the shared tokens. The neutrals used to be a shade darker
|
||||
@@ -269,7 +270,7 @@ internal fun DetailBackdrop(
|
||||
alignment = Alignment.TopCenter,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer { alpha = artworkAlpha().coerceIn(0f, 1f) },
|
||||
.cinematicBackdropPullBack(artwork, artworkAlpha),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
@@ -637,6 +638,7 @@ private fun DetailHero(
|
||||
// height instead would re-flow the title, the synopsis and the action row on every frame
|
||||
// of the transition — which is the "suddenly resize a focused item" failure, sixty times
|
||||
// a second.
|
||||
MembyTrialTypography {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -837,6 +839,7 @@ private fun DetailHero(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1369,6 +1372,7 @@ internal fun DetailFocusablePane(
|
||||
|
||||
@Composable
|
||||
internal fun DetailMetaRows(rows: List<TechnicalSpec>, modifier: Modifier = Modifier, labelWidth: androidx.compose.ui.unit.Dp = 110.dp) {
|
||||
MembyTrialTypography {
|
||||
Column(modifier, verticalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
rows.forEach { row ->
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
@@ -1377,6 +1381,7 @@ internal fun DetailMetaRows(rows: List<TechnicalSpec>, modifier: Modifier = Modi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1394,6 +1399,7 @@ internal fun DetailOverviewPane(
|
||||
modifier: Modifier = Modifier,
|
||||
supportingText: String? = null,
|
||||
) {
|
||||
MembyTrialTypography {
|
||||
DetailFocusablePane(focusRequester, modifier) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(48.dp)) {
|
||||
Column(Modifier.weight(1.5f)) {
|
||||
@@ -1426,6 +1432,7 @@ internal fun DetailOverviewPane(
|
||||
DetailMetaRows(credits, Modifier.weight(1f), labelWidth = 96.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1451,6 +1458,7 @@ internal fun DetailDetailsPane(
|
||||
val alreadySaid = rows.map { it.value }.toSet()
|
||||
specs.filterNot { it.value in alreadySaid }
|
||||
}
|
||||
MembyTrialTypography {
|
||||
DetailFocusablePane(focusRequester, modifier) {
|
||||
if (rows.isEmpty() && specs.isEmpty()) {
|
||||
Text(
|
||||
@@ -1477,6 +1485,7 @@ internal fun DetailDetailsPane(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ class MainActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val remoteConfig by ServiceLocator.remoteConfig.activeFlow.collectAsStateWithLifecycle()
|
||||
MembyTheme {
|
||||
MembyTheme(fontFamilyName = remoteConfig.presentation.fontFamily) {
|
||||
AppRoot(
|
||||
remoteConfig = remoteConfig,
|
||||
onCloseSettings = ::finish,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.ponzischeme89.memby.ui
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
@@ -10,14 +7,12 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -26,6 +21,7 @@ import coil.request.ImageRequest
|
||||
import com.ponzischeme89.memby.ServiceLocator
|
||||
import com.ponzischeme89.memby.data.model.BaseItem
|
||||
import com.ponzischeme89.memby.ui.theme.MembySurface
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTrialTypography
|
||||
|
||||
/**
|
||||
* The quiet surface between the focused title details and the scrolling shelves.
|
||||
@@ -44,51 +40,53 @@ internal fun MetadataHero(
|
||||
isContinueWatchingItem: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clipToBounds()
|
||||
.background(MembySurface),
|
||||
) {
|
||||
MetadataHeroArtwork(
|
||||
item = item,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
MembyTrialTypography {
|
||||
Box(
|
||||
Modifier.fillMaxSize().background(
|
||||
Brush.horizontalGradient(
|
||||
0f to MembySurface,
|
||||
0.30f to MembySurface,
|
||||
0.48f to MembySurface.copy(alpha = 0.80f),
|
||||
0.62f to MembySurface.copy(alpha = 0.40f),
|
||||
0.78f to MembySurface.copy(alpha = 0.10f),
|
||||
1f to Color.Transparent,
|
||||
modifier = modifier
|
||||
.clipToBounds()
|
||||
.background(MembySurface),
|
||||
) {
|
||||
MetadataHeroArtwork(
|
||||
item = item,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
Box(
|
||||
Modifier.fillMaxSize().background(
|
||||
Brush.horizontalGradient(
|
||||
0f to MembySurface,
|
||||
0.30f to MembySurface,
|
||||
0.48f to MembySurface.copy(alpha = 0.80f),
|
||||
0.62f to MembySurface.copy(alpha = 0.40f),
|
||||
0.78f to MembySurface.copy(alpha = 0.10f),
|
||||
1f to Color.Transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
Box(
|
||||
Modifier.fillMaxSize().background(
|
||||
Brush.verticalGradient(
|
||||
0f to MembySurface.copy(alpha = 0.18f),
|
||||
0.58f to Color.Transparent,
|
||||
1f to MembySurface.copy(alpha = 0.20f),
|
||||
)
|
||||
Box(
|
||||
Modifier.fillMaxSize().background(
|
||||
Brush.verticalGradient(
|
||||
0f to MembySurface.copy(alpha = 0.18f),
|
||||
0.58f to Color.Transparent,
|
||||
1f to MembySurface.copy(alpha = 0.20f),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
MediaMetadataPanel(
|
||||
item = item,
|
||||
loading = loading,
|
||||
contentOrder = contentOrder,
|
||||
timeRemainingColour = timeRemainingColour,
|
||||
isContinueWatchingItem = isContinueWatchingItem,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = HomeContentHorizontalInset,
|
||||
end = HomeContentHorizontalInset,
|
||||
top = 28.dp,
|
||||
bottom = 18.dp,
|
||||
),
|
||||
)
|
||||
)
|
||||
MediaMetadataPanel(
|
||||
item = item,
|
||||
loading = loading,
|
||||
contentOrder = contentOrder,
|
||||
timeRemainingColour = timeRemainingColour,
|
||||
isContinueWatchingItem = isContinueWatchingItem,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = HomeContentHorizontalInset,
|
||||
end = HomeContentHorizontalInset,
|
||||
top = 28.dp,
|
||||
bottom = 18.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,16 +117,6 @@ private fun MetadataHeroArtwork(
|
||||
}
|
||||
}
|
||||
if (request == null) return
|
||||
val artworkScale = remember(artwork) { Animatable(MetadataHeroArtworkInitialScale) }
|
||||
LaunchedEffect(artwork) {
|
||||
artworkScale.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = MetadataHeroArtworkZoomOutMs,
|
||||
easing = LinearOutSlowInEasing,
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -143,10 +131,7 @@ private fun MetadataHeroArtwork(
|
||||
alignment = Alignment.CenterEnd,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
scaleX = artworkScale.value
|
||||
scaleY = artworkScale.value
|
||||
},
|
||||
.cinematicBackdropPullBack(artwork),
|
||||
)
|
||||
Box(
|
||||
Modifier.fillMaxSize().background(
|
||||
@@ -173,6 +158,4 @@ private fun MetadataHeroArtwork(
|
||||
}
|
||||
|
||||
private const val MetadataHeroArtworkCrossfadeMs = 180
|
||||
private const val MetadataHeroArtworkInitialScale = 1.05f
|
||||
private const val MetadataHeroArtworkZoomOutMs = 10_000
|
||||
private const val MetadataHeroArtworkWidthFraction = 0.68f
|
||||
|
||||
@@ -32,9 +32,10 @@ annotation class TvPreview
|
||||
@Composable
|
||||
fun PreviewSurface(
|
||||
alignment: Alignment = Alignment.Center,
|
||||
fontFamilyName: String = "system",
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
MembyTheme {
|
||||
MembyTheme(fontFamilyName = fontFamilyName) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
@@ -19,14 +19,12 @@ import com.ponzischeme89.memby.ui.theme.MembyAccent
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccentBright
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccentInk
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccentMuted
|
||||
import com.ponzischeme89.memby.ui.theme.MembyControlSurface
|
||||
import com.ponzischeme89.memby.ui.theme.MembyHairline
|
||||
import com.ponzischeme89.memby.ui.theme.MembyMutedText
|
||||
import com.ponzischeme89.memby.ui.theme.MembyOnSurface
|
||||
import com.ponzischeme89.memby.ui.theme.MembyOutline
|
||||
import com.ponzischeme89.memby.ui.theme.MembyPanelCorner
|
||||
import com.ponzischeme89.memby.ui.theme.MembyQuietText
|
||||
import com.ponzischeme89.memby.ui.theme.MembySurfaceRaised
|
||||
|
||||
/**
|
||||
* One answer in a television settings panel. The panel owns its invariant chrome while
|
||||
@@ -204,7 +202,7 @@ fun tvSettingsOptionView(
|
||||
|
||||
fun render(focused: Boolean) {
|
||||
val fill = when {
|
||||
focused -> MembyControlSurface.toArgb()
|
||||
focused -> PlayerMenuFocusedSurface
|
||||
prominent -> MembyAccentMuted.toArgb()
|
||||
selected -> MembyAccentMuted.toArgb()
|
||||
else -> android.graphics.Color.TRANSPARENT
|
||||
@@ -243,7 +241,7 @@ fun applyTvSettingsPanelSurface(view: View) {
|
||||
val context = view.context
|
||||
view.background = roundedBackground(
|
||||
context,
|
||||
MembySurfaceRaised.toArgb(),
|
||||
PlayerMenuBackground,
|
||||
MembyOutline.toArgb(),
|
||||
1,
|
||||
MembyPanelCorner.value.toInt(),
|
||||
@@ -259,10 +257,10 @@ private data class TvSettingsPanel(
|
||||
private fun tvSettingsPanel(context: Context, title: String, description: String): TvSettingsPanel {
|
||||
val root = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(context.dp(28), context.dp(25), context.dp(28), context.dp(25))
|
||||
setPadding(context.dp(24), context.dp(22), context.dp(24), context.dp(22))
|
||||
background = roundedBackground(
|
||||
context,
|
||||
MembySurfaceRaised.toArgb(),
|
||||
PlayerMenuBackground,
|
||||
MembyOutline.toArgb(),
|
||||
1,
|
||||
MembyPanelCorner.value.toInt(),
|
||||
@@ -272,7 +270,7 @@ private fun tvSettingsPanel(context: Context, title: String, description: String
|
||||
root.addView(TextView(context).apply {
|
||||
text = title
|
||||
setTextColor(MembyOnSurface.toArgb())
|
||||
textSize = 24f
|
||||
textSize = 22f
|
||||
typeface = Typeface.create("sans-serif", Typeface.BOLD)
|
||||
})
|
||||
if (description.isNotBlank()) {
|
||||
@@ -304,7 +302,7 @@ private fun tvSettingsDialog(context: Context, onDismiss: () -> Unit): Dialog =
|
||||
window?.apply {
|
||||
setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT))
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes = attributes.apply { dimAmount = 0.72f }
|
||||
attributes = attributes.apply { dimAmount = 0.80f }
|
||||
setGravity(Gravity.CENTER)
|
||||
}
|
||||
setOnDismissListener { onDismiss() }
|
||||
@@ -320,7 +318,7 @@ private fun finishTvSettingsDialog(
|
||||
val context = root.context
|
||||
val frame = FrameLayout(context).apply {
|
||||
setPadding(context.dp(8), context.dp(8), context.dp(8), context.dp(8))
|
||||
addView(root, FrameLayout.LayoutParams(context.dp(560), ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
addView(root, FrameLayout.LayoutParams(context.dp(480), ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
}
|
||||
dialog.setContentView(frame)
|
||||
val scroll = options.parent as ScrollView
|
||||
@@ -354,7 +352,7 @@ private fun tvSettingsAction(context: Context, label: String, primary: Boolean,
|
||||
context,
|
||||
when {
|
||||
focused && primary -> MembyAccent.toArgb()
|
||||
focused -> MembyControlSurface.toArgb()
|
||||
focused -> PlayerMenuFocusedSurface
|
||||
primary -> MembyAccentMuted.toArgb()
|
||||
else -> android.graphics.Color.TRANSPARENT
|
||||
},
|
||||
@@ -385,3 +383,6 @@ private fun roundedBackground(
|
||||
}
|
||||
|
||||
private fun Context.dp(value: Int): Int = (value * resources.displayMetrics.density).toInt()
|
||||
|
||||
private val PlayerMenuBackground = android.graphics.Color.BLACK
|
||||
private val PlayerMenuFocusedSurface = 0xFF151719.toInt()
|
||||
|
||||
+12
-9
@@ -9,6 +9,7 @@ import com.ponzischeme89.memby.data.model.BaseItem
|
||||
import com.ponzischeme89.memby.ui.ResumableMediaCard
|
||||
import com.ponzischeme89.memby.ui.responsiveRowCardWidth
|
||||
import com.ponzischeme89.memby.ui.toResumableMediaCardModel
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTrialTypography
|
||||
|
||||
@Composable
|
||||
fun ContinueWatchingCard(
|
||||
@@ -46,13 +47,15 @@ fun ContinueWatchingCard(
|
||||
primaryUrl = repository.primaryUrl(item, 480),
|
||||
)
|
||||
}
|
||||
ResumableMediaCard(
|
||||
model = model,
|
||||
width = width,
|
||||
portraitArtwork = portraitArtwork,
|
||||
onFocused = onFocused,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
modifier = modifier
|
||||
)
|
||||
MembyTrialTypography {
|
||||
ResumableMediaCard(
|
||||
model = model,
|
||||
width = width,
|
||||
portraitArtwork = portraitArtwork,
|
||||
onFocused = onFocused,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ private fun MetadataTimeRemaining(
|
||||
label,
|
||||
color = if (colour.equals("white", ignoreCase = true)) Color.White else EmbyGreen,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
lineHeight = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
@@ -81,6 +81,7 @@ import com.ponzischeme89.memby.ui.theme.MembyMutedText
|
||||
import com.ponzischeme89.memby.ui.theme.MembyPanelCorner
|
||||
import com.ponzischeme89.memby.ui.theme.MembyQuietText
|
||||
import com.ponzischeme89.memby.ui.theme.MembySurface
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTrialTypography
|
||||
import com.ponzischeme89.memby.data.remoteconfig.BundledRemoteConfig
|
||||
import com.ponzischeme89.memby.data.remoteconfig.NavigationLabels
|
||||
import com.ponzischeme89.memby.data.remoteconfig.NavigationRemoteConfig
|
||||
@@ -197,6 +198,7 @@ fun TvNavigationRail(
|
||||
animationSpec = tween(160),
|
||||
label = "navigation-rail-width",
|
||||
)
|
||||
MembyTrialTypography {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.width(TvRailCollapsedWidth)
|
||||
@@ -362,6 +364,7 @@ fun TvNavigationRail(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configuredNavigationLabel(
|
||||
|
||||
@@ -4553,7 +4553,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
title = "Playback options",
|
||||
description = "Fine-tune this video without leaving playback.",
|
||||
options = options,
|
||||
onDismiss = { playerView?.showController() },
|
||||
onDismiss = { restorePlayerControlFocus(androidx.media3.ui.R.id.exo_settings) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4579,7 +4579,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
showManualPassthroughPicker(current.codecs)
|
||||
},
|
||||
),
|
||||
onDismiss = { playerView?.showController() },
|
||||
onDismiss = { restorePlayerControlFocus(androidx.media3.ui.R.id.exo_settings) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4598,7 +4598,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
SurroundCodec.entries.filterIndexed { index, _ -> index in selected }.toSet(),
|
||||
)
|
||||
},
|
||||
onDismiss = { playerView?.showController() },
|
||||
onDismiss = { restorePlayerControlFocus(androidx.media3.ui.R.id.exo_settings) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4632,7 +4632,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
},
|
||||
)
|
||||
},
|
||||
onDismiss = { playerView?.showController() },
|
||||
onDismiss = { restorePlayerControlFocus(androidx.media3.ui.R.id.exo_settings) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5197,7 +5197,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
|
||||
private fun hideSubtitleOverlay() {
|
||||
subtitleOverlay?.visibility = View.GONE
|
||||
playerView?.showController()
|
||||
restorePlayerControlFocus(androidx.media3.ui.R.id.exo_subtitle)
|
||||
}
|
||||
|
||||
/** A completed choice returns directly to the programme, without reopening transport. */
|
||||
@@ -5354,14 +5354,33 @@ class PlayerActivity : ComponentActivity() {
|
||||
)
|
||||
subtitleAutoSelectionAttempted = true
|
||||
}
|
||||
if (trackType == C.TRACK_TYPE_AUDIO) playerView?.showController()
|
||||
if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||
restorePlayerControlFocus(androidx.media3.ui.R.id.exo_settings)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
onDismiss = {
|
||||
restorePlayerControlFocus(
|
||||
if (trackType == C.TRACK_TYPE_TEXT) {
|
||||
androidx.media3.ui.R.id.exo_subtitle
|
||||
} else {
|
||||
androidx.media3.ui.R.id.exo_settings
|
||||
},
|
||||
)
|
||||
},
|
||||
onDismiss = { playerView?.showController() },
|
||||
)
|
||||
}
|
||||
|
||||
/** Reopens the transport and returns the remote to the control that launched a menu. */
|
||||
private fun restorePlayerControlFocus(controlId: Int) {
|
||||
val view = playerView ?: return
|
||||
view.showController()
|
||||
view.post {
|
||||
if (view.findViewById<View>(controlId)?.requestFocus() != true) view.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun selectedTrackLabel(trackType: Int): String {
|
||||
val playback = player ?: return ""
|
||||
|
||||
@@ -5,6 +5,9 @@ import android.os.Build
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.ponzischeme89.memby.ServiceLocator
|
||||
import com.ponzischeme89.memby.ui.player.PlayerActivity
|
||||
import com.ponzischeme89.memby.data.model.TrailerPlaybackRequest
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTheme
|
||||
@@ -24,7 +27,8 @@ class ScreensaverActivity : ComponentActivity() {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
||||
}
|
||||
setContent {
|
||||
MembyTheme {
|
||||
val remoteConfig by ServiceLocator.remoteConfig.activeFlow.collectAsStateWithLifecycle()
|
||||
MembyTheme(fontFamilyName = remoteConfig.presentation.fontFamily) {
|
||||
ScreensaverContent(
|
||||
onPlay = { item ->
|
||||
startActivity(
|
||||
|
||||
@@ -73,13 +73,13 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.LocalTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.ponzischeme89.memby.R
|
||||
@@ -103,6 +103,7 @@ import com.ponzischeme89.memby.data.parseThemeColor
|
||||
import com.ponzischeme89.memby.ui.PreviewSurface
|
||||
import com.ponzischeme89.memby.ui.TvPreview
|
||||
import com.ponzischeme89.memby.ui.WelcomeQuoteStyle
|
||||
import com.ponzischeme89.memby.ui.theme.MembyTrialTypography
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccent
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccentBright
|
||||
import com.ponzischeme89.memby.ui.theme.MembyAccentInk
|
||||
@@ -694,6 +695,7 @@ fun SettingsSheet(
|
||||
},
|
||||
)
|
||||
|
||||
MembyTrialTypography {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
if (overlay) {
|
||||
Box(
|
||||
@@ -755,6 +757,7 @@ fun SettingsSheet(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SettingsPanelContent(
|
||||
@@ -786,6 +789,7 @@ internal fun SettingsPanelContent(
|
||||
contentScrollState.scrollTo(0)
|
||||
}
|
||||
|
||||
MembyTrialTypography {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.then(if (overlay) Modifier.width(760.dp) else Modifier.fillMaxWidth())
|
||||
@@ -1109,6 +1113,8 @@ internal fun SettingsPanelContent(
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1269,7 +1275,7 @@ private fun DeviceRenameDialog(
|
||||
onValueChange = { if (it.length <= 80) name = it },
|
||||
singleLine = true,
|
||||
enabled = !busy,
|
||||
textStyle = TextStyle(color = TextPrimary, fontSize = 18.sp),
|
||||
textStyle = LocalTextStyle.current.copy(color = TextPrimary, fontSize = 18.sp),
|
||||
cursorBrush = SolidColor(EmbyGreen),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { if (valid && !busy) onSave(name) }),
|
||||
|
||||
@@ -2,12 +2,34 @@ package com.ponzischeme89.memby.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.tv.material3.LocalTextStyle
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.darkColorScheme
|
||||
import com.ponzischeme89.memby.R
|
||||
|
||||
internal enum class AppFontFamily {
|
||||
SYSTEM,
|
||||
INTER,
|
||||
}
|
||||
|
||||
internal fun appFontFamily(value: String?): AppFontFamily = when (value?.trim()?.lowercase()) {
|
||||
"inter" -> AppFontFamily.INTER
|
||||
else -> AppFontFamily.SYSTEM
|
||||
}
|
||||
|
||||
private val InterFontFamily = FontFamily(
|
||||
Font(R.font.inter_regular, FontWeight.Normal),
|
||||
Font(R.font.inter_medium, FontWeight.Medium),
|
||||
Font(R.font.inter_semibold, FontWeight.SemiBold),
|
||||
Font(R.font.inter_bold, FontWeight.Bold),
|
||||
)
|
||||
|
||||
private val LocalTrialFontFamily = compositionLocalOf { FontFamily.SansSerif }
|
||||
|
||||
// The scheme is the same near-blacks the screens actually paint (see DesignTokens.kt), so
|
||||
// a component that falls back to a theme colour lands on the surface it is sitting on
|
||||
@@ -25,7 +47,10 @@ private fun embyColors() = darkColorScheme(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MembyTheme(content: @Composable () -> Unit) {
|
||||
fun MembyTheme(
|
||||
fontFamilyName: String = "system",
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
// Use Android's complete Roboto-backed sans family rather than pinning every label
|
||||
// to sans-serif-medium. The generic family lets Compose select real regular,
|
||||
// medium, semibold and bold faces from each Text's FontWeight, restoring hierarchy
|
||||
@@ -41,9 +66,23 @@ fun MembyTheme(content: @Composable () -> Unit) {
|
||||
letterSpacing = 0.006.em,
|
||||
lineHeight = 1.22.em,
|
||||
)
|
||||
val trialFontFamily = when (appFontFamily(fontFamilyName)) {
|
||||
AppFontFamily.SYSTEM -> FontFamily.SansSerif
|
||||
AppFontFamily.INTER -> InterFontFamily
|
||||
}
|
||||
MaterialTheme(colorScheme = embyColors()) {
|
||||
CompositionLocalProvider(LocalTextStyle provides appTextStyle) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides appTextStyle,
|
||||
LocalTrialFontFamily provides trialFontFamily,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies the server-selected family only to the surfaces included in the visual trial. */
|
||||
@Composable
|
||||
fun MembyTrialTypography(content: @Composable () -> Unit) {
|
||||
val textStyle = LocalTextStyle.current.copy(fontFamily = LocalTrialFontFamily.current)
|
||||
CompositionLocalProvider(LocalTextStyle provides textStyle, content = content)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user