0.3.03
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- A simple outlined cog that matches Memby's other player glyphs. Kept app-owned so a
|
||||
Media3 update cannot silently replace the settings entry with platform-era artwork. -->
|
||||
<!-- A centred, even-odd cog that stays legible under the player button's tint. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,8.75a3.25,3.25 0,1 0,0 6.5a3.25,3.25 0,1 0,0 -6.5M19.1,13.35c0.05,-0.44 0.05,-0.88 0,-1.32l1.66,-1.29l-1.85,-3.2l-1.94,0.78a7.7,7.7 0,0 0,-1.15 -0.67L15.53,5.6h-3.7l-0.29,2.05c-0.4,0.18 -0.79,0.4 -1.15,0.67L8.45,7.54l-1.85,3.2l1.66,1.29a6.1,6.1 0,0 0,0 1.32L6.6,14.64l1.85,3.2l1.94,-0.78c0.36,0.27 0.75,0.49 1.15,0.67l0.29,2.05h3.7l0.29,-2.05c0.4,-0.18 0.79,-0.4 1.15,-0.67l1.94,0.78l1.85,-3.2z"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.7" />
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.65 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.37,-0.31 -0.60,-0.22l-2.49,1c-0.52,-0.40 -1.08,-0.73 -1.69,-0.98L14.5,2.42C14.47,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42L9.13,5.07c-0.61,0.25 -1.18,0.58 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.48,0 -0.60,0.22l-2,3.46c-0.12,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.08,0.66 -0.08,0.98s0.04,0.66 0.08,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.37,0.31 0.60,0.22l2.49,-1c0.51,0.40 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.47,-0.18 0.50,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.58 1.69,-0.98l2.49,1c0.23,0.09 0.48,0 0.60,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64zM12,8.5a3.5,3.5 0,1 0,0 7a3.5,3.5 0,1 0,0 -7z" />
|
||||
</vector>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
the only lit thing on it. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F7000000" />
|
||||
<solid android:color="#FF000000" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#1FFFFFFF" />
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,6 +13,7 @@ class MembyRemoteConfigTest {
|
||||
assertEquals(0, bundled.configVersion)
|
||||
assertEquals("Favourites", bundled.navigation.labels.favourites)
|
||||
assertEquals(184, bundled.navigation.expandedWidthDp)
|
||||
assertEquals("system", bundled.presentation.fontFamily)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -31,6 +32,17 @@ class MembyRemoteConfigTest {
|
||||
assertEquals(true, document.continueWatching.enabled)
|
||||
assertEquals(50, document.search.maxResults)
|
||||
assertEquals("automatic", document.ui.artworkStyle)
|
||||
assertEquals("system", document.presentation.fontFamily)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun interFontFamilyDecodesFromTheServerDocument() {
|
||||
val raw = validDocument.replace(
|
||||
"\"navigationContentShiftDp\": 120",
|
||||
"\"navigationContentShiftDp\": 120, \"fontFamily\": \"inter\"",
|
||||
)
|
||||
|
||||
assertEquals("inter", decodeRemoteConfig(raw)!!.presentation.fontFamily)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ponzischeme89.memby.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.v2.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import com.ponzischeme89.memby.ServiceLocator
|
||||
import com.ponzischeme89.memby.data.model.BaseItem
|
||||
import com.ponzischeme89.memby.data.model.UserItemData
|
||||
import com.ponzischeme89.memby.ui.components.media.ContinueWatchingCard
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
@Config(sdk = [34], qualifiers = "w960dp-h540dp-television-xhdpi")
|
||||
class ContinueWatchingFontScreenshotTest {
|
||||
@get:Rule
|
||||
val compose = createComposeRule()
|
||||
|
||||
@Before
|
||||
fun locator() {
|
||||
ServiceLocator.init(ApplicationProvider.getApplicationContext())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Inter keeps continue watching metadata within the card`() {
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") {
|
||||
Box(Modifier.fillMaxSize().padding(48.dp)) {
|
||||
ContinueWatchingCard(
|
||||
item = BaseItem(
|
||||
id = "episode-1",
|
||||
name = "A Harbour at the Edge of Morning",
|
||||
type = "Episode",
|
||||
seriesName = "The Longest Northbound Winter",
|
||||
parentIndexNumber = 3,
|
||||
indexNumber = 7,
|
||||
runTimeTicks = 52L * 60L * 10_000_000L,
|
||||
userData = UserItemData(
|
||||
playbackPositionTicks = 19L * 60L * 10_000_000L,
|
||||
),
|
||||
),
|
||||
availableWidth = 864.dp,
|
||||
portraitArtwork = false,
|
||||
onFocused = {},
|
||||
onClick = {},
|
||||
onLongClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.onNodeWithText("The Longest Northbound Winter").assertExists()
|
||||
compose.onNodeWithText("S03E07 · A Harbour at the Edge of Morning").assertExists()
|
||||
compose.onRoot().captureRoboImage(
|
||||
"build/screenshots/typography/continue-watching-inter.png",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ class DetailPageScreenshotTest {
|
||||
|
||||
private fun capturePane(name: String, pane: @Composable () -> Unit) {
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) {
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -293,14 +293,14 @@ class DetailPageScreenshotTest {
|
||||
|
||||
private fun capture(name: String, content: @Composable () -> Unit) {
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) { content() }
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") { content() }
|
||||
}
|
||||
compose.onRoot().captureRoboImage("build/screenshots/detail-page/$name.png")
|
||||
}
|
||||
|
||||
private fun captureTab(name: String, tab: String, content: @Composable () -> Unit) {
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) { content() }
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") { content() }
|
||||
}
|
||||
compose.onNodeWithText(tab).performClick()
|
||||
compose.onRoot().captureRoboImage("build/screenshots/detail-page/$name.png")
|
||||
|
||||
@@ -165,7 +165,7 @@ class HomeMovieHeroScreenshotTest {
|
||||
)
|
||||
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) {
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Image(
|
||||
bitmap = artwork,
|
||||
@@ -262,7 +262,7 @@ class HomeMovieHeroScreenshotTest {
|
||||
)
|
||||
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) {
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Image(
|
||||
bitmap = artwork,
|
||||
@@ -293,7 +293,7 @@ class HomeMovieHeroScreenshotTest {
|
||||
val railFocus = FocusRequester()
|
||||
|
||||
compose.setContent {
|
||||
PreviewSurface(alignment = Alignment.TopStart) {
|
||||
PreviewSurface(alignment = Alignment.TopStart, fontFamilyName = "inter") {
|
||||
Row(Modifier.fillMaxSize()) {
|
||||
TvNavigationRail(
|
||||
selected = BrowseDestination.HOME,
|
||||
|
||||
+5
-2
@@ -179,7 +179,7 @@ class SettingsSheetScreenshotTest {
|
||||
fun record(title: String, enabled: Boolean) {
|
||||
if (enabled) onToggled(title)
|
||||
}
|
||||
PreviewSurface {
|
||||
PreviewSurface(fontFamilyName = "inter") {
|
||||
SettingsPanelContent(
|
||||
state = SettingsPanelState(
|
||||
showLogo = showLogo,
|
||||
@@ -237,7 +237,10 @@ class SettingsSheetScreenshotTest {
|
||||
) {
|
||||
val firstFocus = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { firstFocus.requestFocus() }
|
||||
PreviewSurface(alignment = if (overlay) Alignment.CenterEnd else Alignment.Center) {
|
||||
PreviewSurface(
|
||||
alignment = if (overlay) Alignment.CenterEnd else Alignment.Center,
|
||||
fontFamilyName = "inter",
|
||||
) {
|
||||
SettingsPanelContent(
|
||||
state = SettingsPanelState(
|
||||
showLogo = true,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ponzischeme89.memby.ui.theme
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class AppFontFamilyTest {
|
||||
@Test
|
||||
fun `system and inter are supported`() {
|
||||
assertEquals(AppFontFamily.SYSTEM, appFontFamily("system"))
|
||||
assertEquals(AppFontFamily.INTER, appFontFamily("inter"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing and unknown values fall back to system`() {
|
||||
assertEquals(AppFontFamily.SYSTEM, appFontFamily(null))
|
||||
assertEquals(AppFontFamily.SYSTEM, appFontFamily(""))
|
||||
assertEquals(AppFontFamily.SYSTEM, appFontFamily("something-new"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user