0.2.94
This commit is contained in:
@@ -64,7 +64,7 @@ val projectNoticeText =
|
|||||||
|
|
||||||
// A release workflow can derive the app version from its Git tag without editing the
|
// A release workflow can derive the app version from its Git tag without editing the
|
||||||
// source tree. Local builds keep using the checked-in default.
|
// source tree. Local builds keep using the checked-in default.
|
||||||
val defaultVersionName = "0.2.93"
|
val defaultVersionName = "0.2.94"
|
||||||
val membyVersionName: String =
|
val membyVersionName: String =
|
||||||
(project.findProperty("memby.versionName") as String?)
|
(project.findProperty("memby.versionName") as String?)
|
||||||
?.trim()
|
?.trim()
|
||||||
|
|||||||
@@ -242,6 +242,8 @@ internal object SettingsMigrationLogic {
|
|||||||
booleanPreferencesKey("show_home_card_metadata")
|
booleanPreferencesKey("show_home_card_metadata")
|
||||||
] ?: true,
|
] ?: true,
|
||||||
showRatingsStrip = preferences[booleanPreferencesKey("show_ratings_strip")] ?: true,
|
showRatingsStrip = preferences[booleanPreferencesKey("show_ratings_strip")] ?: true,
|
||||||
|
metadataHeroContentOrder = preferences[stringPreferencesKey("metadata_hero_content_order")]
|
||||||
|
?: UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
hideWatchedMovies = preferences[booleanPreferencesKey("hide_watched_movies")] ?: false,
|
hideWatchedMovies = preferences[booleanPreferencesKey("hide_watched_movies")] ?: false,
|
||||||
homeRowOrder = preferences[stringPreferencesKey("home_row_order")].orEmpty(),
|
homeRowOrder = preferences[stringPreferencesKey("home_row_order")].orEmpty(),
|
||||||
homePinnedRows = preferences[stringPreferencesKey("home_pinned_rows")].orEmpty(),
|
homePinnedRows = preferences[stringPreferencesKey("home_pinned_rows")].orEmpty(),
|
||||||
@@ -363,6 +365,8 @@ data class Settings(
|
|||||||
val showHomeCardMetadata: Boolean = true,
|
val showHomeCardMetadata: Boolean = true,
|
||||||
/** Show third-party ratings on browse and detail surfaces. Profile-specific. */
|
/** Show third-party ratings on browse and detail surfaces. Profile-specific. */
|
||||||
val showRatingsStrip: Boolean = true,
|
val showRatingsStrip: Boolean = true,
|
||||||
|
/** Newline-separated, operator-controlled content order for the focused metadata hero. */
|
||||||
|
val metadataHeroContentOrder: String = UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
/** Hide fully watched movies from browse rows and hero selections. */
|
/** Hide fully watched movies from browse rows and hero selections. */
|
||||||
val hideWatchedMovies: Boolean = false,
|
val hideWatchedMovies: Boolean = false,
|
||||||
/** Newline-separated server row ids. These are profile-specific home customisations. */
|
/** Newline-separated server row ids. These are profile-specific home customisations. */
|
||||||
@@ -511,6 +515,7 @@ data class EmbyProfile(
|
|||||||
val homeArtworkStyle: String = Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
val homeArtworkStyle: String = Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
||||||
val showHomeCardMetadata: Boolean = true,
|
val showHomeCardMetadata: Boolean = true,
|
||||||
val showRatingsStrip: Boolean = true,
|
val showRatingsStrip: Boolean = true,
|
||||||
|
val metadataHeroContentOrder: String = UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
val hideWatchedMovies: Boolean = false,
|
val hideWatchedMovies: Boolean = false,
|
||||||
val homeRowOrder: String = "",
|
val homeRowOrder: String = "",
|
||||||
val homePinnedRows: String = "",
|
val homePinnedRows: String = "",
|
||||||
@@ -590,6 +595,7 @@ class SettingsStore(private val context: Context) {
|
|||||||
val HOME_ARTWORK_STYLE = stringPreferencesKey("home_artwork_style")
|
val HOME_ARTWORK_STYLE = stringPreferencesKey("home_artwork_style")
|
||||||
val SHOW_HOME_CARD_METADATA = booleanPreferencesKey("show_home_card_metadata")
|
val SHOW_HOME_CARD_METADATA = booleanPreferencesKey("show_home_card_metadata")
|
||||||
val SHOW_RATINGS_STRIP = booleanPreferencesKey("show_ratings_strip")
|
val SHOW_RATINGS_STRIP = booleanPreferencesKey("show_ratings_strip")
|
||||||
|
val METADATA_HERO_CONTENT_ORDER = stringPreferencesKey("metadata_hero_content_order")
|
||||||
val HIDE_WATCHED_MOVIES = booleanPreferencesKey("hide_watched_movies")
|
val HIDE_WATCHED_MOVIES = booleanPreferencesKey("hide_watched_movies")
|
||||||
val HOME_ROW_ORDER = stringPreferencesKey("home_row_order")
|
val HOME_ROW_ORDER = stringPreferencesKey("home_row_order")
|
||||||
val HOME_PINNED_ROWS = stringPreferencesKey("home_pinned_rows")
|
val HOME_PINNED_ROWS = stringPreferencesKey("home_pinned_rows")
|
||||||
@@ -862,6 +868,7 @@ class SettingsStore(private val context: Context) {
|
|||||||
store[Keys.HOME_ARTWORK_STYLE] = preferences.homeArtworkStyle
|
store[Keys.HOME_ARTWORK_STYLE] = preferences.homeArtworkStyle
|
||||||
store[Keys.SHOW_HOME_CARD_METADATA] = preferences.showHomeCardMetadata
|
store[Keys.SHOW_HOME_CARD_METADATA] = preferences.showHomeCardMetadata
|
||||||
store[Keys.SHOW_RATINGS_STRIP] = preferences.showRatingsStrip
|
store[Keys.SHOW_RATINGS_STRIP] = preferences.showRatingsStrip
|
||||||
|
store[Keys.METADATA_HERO_CONTENT_ORDER] = preferences.metadataHeroContentOrder.joinToString("\n")
|
||||||
store[Keys.HIDE_WATCHED_MOVIES] = preferences.hideWatchedMovies
|
store[Keys.HIDE_WATCHED_MOVIES] = preferences.hideWatchedMovies
|
||||||
store[Keys.SHOW_TITLE_LOGO] = preferences.showTitleLogo
|
store[Keys.SHOW_TITLE_LOGO] = preferences.showTitleLogo
|
||||||
store[Keys.WELCOME_QUOTE_STYLE] = preferences.welcomeQuoteStyle
|
store[Keys.WELCOME_QUOTE_STYLE] = preferences.welcomeQuoteStyle
|
||||||
@@ -888,6 +895,7 @@ class SettingsStore(private val context: Context) {
|
|||||||
homeArtworkStyle = preferences.homeArtworkStyle,
|
homeArtworkStyle = preferences.homeArtworkStyle,
|
||||||
showHomeCardMetadata = preferences.showHomeCardMetadata,
|
showHomeCardMetadata = preferences.showHomeCardMetadata,
|
||||||
showRatingsStrip = preferences.showRatingsStrip,
|
showRatingsStrip = preferences.showRatingsStrip,
|
||||||
|
metadataHeroContentOrder = preferences.metadataHeroContentOrder.joinToString("\n"),
|
||||||
hideWatchedMovies = preferences.hideWatchedMovies,
|
hideWatchedMovies = preferences.hideWatchedMovies,
|
||||||
showTitleLogo = preferences.showTitleLogo,
|
showTitleLogo = preferences.showTitleLogo,
|
||||||
welcomeQuoteStyle = preferences.welcomeQuoteStyle,
|
welcomeQuoteStyle = preferences.welcomeQuoteStyle,
|
||||||
@@ -1348,6 +1356,8 @@ class SettingsStore(private val context: Context) {
|
|||||||
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
||||||
showHomeCardMetadata = previous?.showHomeCardMetadata ?: true,
|
showHomeCardMetadata = previous?.showHomeCardMetadata ?: true,
|
||||||
showRatingsStrip = previous?.showRatingsStrip ?: true,
|
showRatingsStrip = previous?.showRatingsStrip ?: true,
|
||||||
|
metadataHeroContentOrder = previous?.metadataHeroContentOrder
|
||||||
|
?: UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
hideWatchedMovies = previous?.hideWatchedMovies ?: false,
|
hideWatchedMovies = previous?.hideWatchedMovies ?: false,
|
||||||
homeRowOrder = previous?.homeRowOrder.orEmpty(),
|
homeRowOrder = previous?.homeRowOrder.orEmpty(),
|
||||||
homePinnedRows = previous?.homePinnedRows.orEmpty(),
|
homePinnedRows = previous?.homePinnedRows.orEmpty(),
|
||||||
@@ -1526,6 +1536,7 @@ class SettingsStore(private val context: Context) {
|
|||||||
preferences[Keys.HOME_ARTWORK_STYLE] = profile.homeArtworkStyle
|
preferences[Keys.HOME_ARTWORK_STYLE] = profile.homeArtworkStyle
|
||||||
preferences[Keys.SHOW_HOME_CARD_METADATA] = profile.showHomeCardMetadata
|
preferences[Keys.SHOW_HOME_CARD_METADATA] = profile.showHomeCardMetadata
|
||||||
preferences[Keys.SHOW_RATINGS_STRIP] = profile.showRatingsStrip
|
preferences[Keys.SHOW_RATINGS_STRIP] = profile.showRatingsStrip
|
||||||
|
preferences[Keys.METADATA_HERO_CONTENT_ORDER] = profile.metadataHeroContentOrder
|
||||||
preferences[Keys.HIDE_WATCHED_MOVIES] = profile.hideWatchedMovies
|
preferences[Keys.HIDE_WATCHED_MOVIES] = profile.hideWatchedMovies
|
||||||
preferences[Keys.HOME_ROW_ORDER] = profile.homeRowOrder
|
preferences[Keys.HOME_ROW_ORDER] = profile.homeRowOrder
|
||||||
preferences[Keys.HOME_PINNED_ROWS] = profile.homePinnedRows
|
preferences[Keys.HOME_PINNED_ROWS] = profile.homePinnedRows
|
||||||
@@ -1569,6 +1580,8 @@ class SettingsStore(private val context: Context) {
|
|||||||
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
||||||
showHomeCardMetadata = preferences[Keys.SHOW_HOME_CARD_METADATA] ?: true,
|
showHomeCardMetadata = preferences[Keys.SHOW_HOME_CARD_METADATA] ?: true,
|
||||||
showRatingsStrip = preferences[Keys.SHOW_RATINGS_STRIP] ?: true,
|
showRatingsStrip = preferences[Keys.SHOW_RATINGS_STRIP] ?: true,
|
||||||
|
metadataHeroContentOrder = preferences[Keys.METADATA_HERO_CONTENT_ORDER]
|
||||||
|
?: UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
hideWatchedMovies = preferences[Keys.HIDE_WATCHED_MOVIES] ?: false,
|
hideWatchedMovies = preferences[Keys.HIDE_WATCHED_MOVIES] ?: false,
|
||||||
homeRowOrder = preferences[Keys.HOME_ROW_ORDER].orEmpty(),
|
homeRowOrder = preferences[Keys.HOME_ROW_ORDER].orEmpty(),
|
||||||
homePinnedRows = preferences[Keys.HOME_PINNED_ROWS].orEmpty(),
|
homePinnedRows = preferences[Keys.HOME_PINNED_ROWS].orEmpty(),
|
||||||
@@ -1637,6 +1650,8 @@ class SettingsStore(private val context: Context) {
|
|||||||
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
?: Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
||||||
showHomeCardMetadata = preferences[Keys.SHOW_HOME_CARD_METADATA] ?: true,
|
showHomeCardMetadata = preferences[Keys.SHOW_HOME_CARD_METADATA] ?: true,
|
||||||
showRatingsStrip = preferences[Keys.SHOW_RATINGS_STRIP] ?: true,
|
showRatingsStrip = preferences[Keys.SHOW_RATINGS_STRIP] ?: true,
|
||||||
|
metadataHeroContentOrder = preferences[Keys.METADATA_HERO_CONTENT_ORDER]
|
||||||
|
?: UserPreferences.DEFAULT_METADATA_HERO_CONTENT_ORDER.joinToString("\n"),
|
||||||
hideWatchedMovies = preferences[Keys.HIDE_WATCHED_MOVIES] ?: false,
|
hideWatchedMovies = preferences[Keys.HIDE_WATCHED_MOVIES] ?: false,
|
||||||
homeRowOrder = preferences[Keys.HOME_ROW_ORDER].orEmpty(),
|
homeRowOrder = preferences[Keys.HOME_ROW_ORDER].orEmpty(),
|
||||||
homePinnedRows = preferences[Keys.HOME_PINNED_ROWS].orEmpty(),
|
homePinnedRows = preferences[Keys.HOME_PINNED_ROWS].orEmpty(),
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ data class UserPreferences(
|
|||||||
val homeArtworkStyle: String = Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
val homeArtworkStyle: String = Settings.DEFAULT_HOME_ARTWORK_STYLE,
|
||||||
val showHomeCardMetadata: Boolean = true,
|
val showHomeCardMetadata: Boolean = true,
|
||||||
val showRatingsStrip: Boolean = true,
|
val showRatingsStrip: Boolean = true,
|
||||||
|
/** Operator-controlled order of the focused metadata hero's content blocks. */
|
||||||
|
val metadataHeroContentOrder: List<String> = DEFAULT_METADATA_HERO_CONTENT_ORDER,
|
||||||
val hideWatchedMovies: Boolean = false,
|
val hideWatchedMovies: Boolean = false,
|
||||||
val showTitleLogo: Boolean = true,
|
val showTitleLogo: Boolean = true,
|
||||||
val welcomeQuoteStyle: String = Settings.DEFAULT_WELCOME_QUOTE_STYLE,
|
val welcomeQuoteStyle: String = Settings.DEFAULT_WELCOME_QUOTE_STYLE,
|
||||||
@@ -73,6 +75,7 @@ data class UserPreferences(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT_SECTIONS: List<String> = Settings.DEFAULT_HOME_SECTIONS.split(",")
|
val DEFAULT_SECTIONS: List<String> = Settings.DEFAULT_HOME_SECTIONS.split(",")
|
||||||
|
val DEFAULT_METADATA_HERO_CONTENT_ORDER = listOf("title", "ratings", "facts", "genres", "summary")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +93,7 @@ fun Settings.toUserPreferences(): UserPreferences = UserPreferences(
|
|||||||
homeArtworkStyle = homeArtworkStyle,
|
homeArtworkStyle = homeArtworkStyle,
|
||||||
showHomeCardMetadata = showHomeCardMetadata,
|
showHomeCardMetadata = showHomeCardMetadata,
|
||||||
showRatingsStrip = showRatingsStrip,
|
showRatingsStrip = showRatingsStrip,
|
||||||
|
metadataHeroContentOrder = metadataHeroContentOrder.decodeLineList(),
|
||||||
hideWatchedMovies = hideWatchedMovies,
|
hideWatchedMovies = hideWatchedMovies,
|
||||||
showTitleLogo = showTitleLogo,
|
showTitleLogo = showTitleLogo,
|
||||||
welcomeQuoteStyle = welcomeQuoteStyle,
|
welcomeQuoteStyle = welcomeQuoteStyle,
|
||||||
@@ -134,6 +138,9 @@ fun decodeUserPreferences(
|
|||||||
homeArtworkStyle = json.string("homeArtworkStyle", fallback.homeArtworkStyle),
|
homeArtworkStyle = json.string("homeArtworkStyle", fallback.homeArtworkStyle),
|
||||||
showHomeCardMetadata = json.boolean("showHomeCardMetadata", fallback.showHomeCardMetadata),
|
showHomeCardMetadata = json.boolean("showHomeCardMetadata", fallback.showHomeCardMetadata),
|
||||||
showRatingsStrip = json.boolean("showRatingsStrip", fallback.showRatingsStrip),
|
showRatingsStrip = json.boolean("showRatingsStrip", fallback.showRatingsStrip),
|
||||||
|
metadataHeroContentOrder = json.stringList(
|
||||||
|
"metadataHeroContentOrder", fallback.metadataHeroContentOrder,
|
||||||
|
),
|
||||||
hideWatchedMovies = json.boolean("hideWatchedMovies", fallback.hideWatchedMovies),
|
hideWatchedMovies = json.boolean("hideWatchedMovies", fallback.hideWatchedMovies),
|
||||||
showTitleLogo = json.boolean("showTitleLogo", fallback.showTitleLogo),
|
showTitleLogo = json.boolean("showTitleLogo", fallback.showTitleLogo),
|
||||||
welcomeQuoteStyle = json.string("welcomeQuoteStyle", fallback.welcomeQuoteStyle),
|
welcomeQuoteStyle = json.string("welcomeQuoteStyle", fallback.welcomeQuoteStyle),
|
||||||
@@ -169,6 +176,7 @@ fun UserPreferences.encode(): JsonObject = buildJsonObject {
|
|||||||
put("homeArtworkStyle", homeArtworkStyle)
|
put("homeArtworkStyle", homeArtworkStyle)
|
||||||
put("showHomeCardMetadata", showHomeCardMetadata)
|
put("showHomeCardMetadata", showHomeCardMetadata)
|
||||||
put("showRatingsStrip", showRatingsStrip)
|
put("showRatingsStrip", showRatingsStrip)
|
||||||
|
putJsonArray("metadataHeroContentOrder") { metadataHeroContentOrder.forEach { add(JsonPrimitive(it)) } }
|
||||||
put("hideWatchedMovies", hideWatchedMovies)
|
put("hideWatchedMovies", hideWatchedMovies)
|
||||||
put("showTitleLogo", showTitleLogo)
|
put("showTitleLogo", showTitleLogo)
|
||||||
put("welcomeQuoteStyle", welcomeQuoteStyle)
|
put("welcomeQuoteStyle", welcomeQuoteStyle)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.ponzischeme89.memby.ui
|
|||||||
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyIcon
|
import com.ponzischeme89.memby.ui.theme.MembyIcon
|
||||||
import com.ponzischeme89.memby.ui.theme.mark
|
import com.ponzischeme89.memby.ui.theme.mark
|
||||||
|
import com.ponzischeme89.memby.data.decodeLineList
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -283,6 +284,7 @@ internal fun FocusedHomeBackdrop(homeViewModel: HomeViewModel) {
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun FocusedHomeMetadata(
|
internal fun FocusedHomeMetadata(
|
||||||
homeViewModel: HomeViewModel,
|
homeViewModel: HomeViewModel,
|
||||||
|
metadataHeroContentOrder: List<String>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val focusedItem by homeViewModel.focusedItem.collectAsStateWithLifecycle()
|
val focusedItem by homeViewModel.focusedItem.collectAsStateWithLifecycle()
|
||||||
@@ -293,6 +295,7 @@ internal fun FocusedHomeMetadata(
|
|||||||
MetadataHero(
|
MetadataHero(
|
||||||
item = focusedItem,
|
item = focusedItem,
|
||||||
loading = homeContent.loading.isNotEmpty(),
|
loading = homeContent.loading.isNotEmpty(),
|
||||||
|
contentOrder = metadataHeroContentOrder,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1376,6 +1376,7 @@ internal fun HomeScreen(
|
|||||||
} else {
|
} else {
|
||||||
FocusedHomeMetadata(
|
FocusedHomeMetadata(
|
||||||
homeViewModel = homeViewModel,
|
homeViewModel = homeViewModel,
|
||||||
|
metadataHeroContentOrder = settings.metadataHeroContentOrder.decodeLineList(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(metadataHeight)
|
.height(metadataHeight)
|
||||||
// LazyColumn is drawn later as a sibling. Keep the hero
|
// LazyColumn is drawn later as a sibling. Keep the hero
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.ponzischeme89.memby.ui.theme.MembySurface
|
|||||||
internal fun MetadataHero(
|
internal fun MetadataHero(
|
||||||
item: BaseItem?,
|
item: BaseItem?,
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
|
contentOrder: List<String> = emptyList(),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
@@ -69,6 +70,7 @@ internal fun MetadataHero(
|
|||||||
MediaMetadataPanel(
|
MediaMetadataPanel(
|
||||||
item = item,
|
item = item,
|
||||||
loading = loading,
|
loading = loading,
|
||||||
|
contentOrder = contentOrder,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(
|
.padding(
|
||||||
|
|||||||
@@ -2,122 +2,55 @@ package com.ponzischeme89.memby.ui
|
|||||||
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyIcon
|
import com.ponzischeme89.memby.ui.theme.MembyIcon
|
||||||
import com.ponzischeme89.memby.ui.theme.mark
|
import com.ponzischeme89.memby.ui.theme.mark
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.core.Animatable
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.focusGroup
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusProperties
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.layout
|
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.input.key.type
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
|
||||||
import androidx.compose.ui.semantics.onLongClick
|
|
||||||
import androidx.compose.ui.semantics.semantics
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import androidx.tv.material3.Icon
|
import androidx.tv.material3.Icon
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.ponzischeme89.memby.ServiceLocator
|
import com.ponzischeme89.memby.ServiceLocator
|
||||||
import com.ponzischeme89.memby.R
|
|
||||||
import com.ponzischeme89.memby.BuildConfig
|
|
||||||
import com.ponzischeme89.memby.data.EmbyProfile
|
|
||||||
import com.ponzischeme89.memby.data.model.BaseItem
|
import com.ponzischeme89.memby.data.model.BaseItem
|
||||||
import com.ponzischeme89.memby.data.model.MembyViewer
|
|
||||||
import com.ponzischeme89.memby.ui.alerts.alertBadgeLabel
|
|
||||||
import com.ponzischeme89.memby.ui.detail.UHD_MIN_WIDTH
|
|
||||||
import com.ponzischeme89.memby.ui.detail.channelLabel
|
|
||||||
import com.ponzischeme89.memby.ui.detail.dynamicRangeLabel
|
|
||||||
import com.ponzischeme89.memby.ui.detail.formatRuntime
|
import com.ponzischeme89.memby.ui.detail.formatRuntime
|
||||||
import com.ponzischeme89.memby.ui.theme.FactSeparator
|
import com.ponzischeme89.memby.ui.theme.FactSeparator
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyAccent
|
import com.ponzischeme89.memby.ui.theme.MembyAccent
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyAccentInk
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyCardCorner
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyChipCorner
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyControlSurface
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyControlSurfaceRaised
|
import com.ponzischeme89.memby.ui.theme.MembyControlSurfaceRaised
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyMutedText
|
import com.ponzischeme89.memby.ui.theme.MembyMutedText
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyOnSurface
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyPanelCorner
|
|
||||||
import com.ponzischeme89.memby.ui.theme.MembyQuietText
|
import com.ponzischeme89.memby.ui.theme.MembyQuietText
|
||||||
import com.ponzischeme89.memby.ui.theme.MembySurface
|
import com.ponzischeme89.memby.ui.theme.MembySurface
|
||||||
import com.ponzischeme89.memby.ui.theme.MembySurfaceRaised
|
|
||||||
import com.ponzischeme89.memby.ui.theme.ValueSeparator
|
import com.ponzischeme89.memby.ui.theme.ValueSeparator
|
||||||
import com.ponzischeme89.memby.data.remoteconfig.BundledRemoteConfig
|
|
||||||
import com.ponzischeme89.memby.data.remoteconfig.NavigationLabels
|
|
||||||
import com.ponzischeme89.memby.data.remoteconfig.NavigationRemoteConfig
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
// The launcher's names for the shared tokens. Both surfaces read from one palette now
|
// The launcher's names for the shared tokens. Both surfaces read from one palette now
|
||||||
@@ -196,6 +129,7 @@ fun BackdropLayer(item: BaseItem?, modifier: Modifier = Modifier) {
|
|||||||
fun MediaMetadataPanel(
|
fun MediaMetadataPanel(
|
||||||
item: BaseItem?,
|
item: BaseItem?,
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
|
contentOrder: List<String>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(modifier) {
|
BoxWithConstraints(modifier) {
|
||||||
@@ -219,7 +153,7 @@ fun MediaMetadataPanel(
|
|||||||
Text("Choose something to watch.", color = MutedText, fontSize = 14.sp)
|
Text("Choose something to watch.", color = MutedText, fontSize = 14.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> MetadataContent(item, contentWidth, compactLayout)
|
else -> MetadataContent(item, contentWidth, compactLayout, contentOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,6 +196,7 @@ private fun MetadataContent(
|
|||||||
item: BaseItem,
|
item: BaseItem,
|
||||||
contentWidth: Dp,
|
contentWidth: Dp,
|
||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
|
contentOrder: List<String>,
|
||||||
) {
|
) {
|
||||||
if (item.isSchedule) {
|
if (item.isSchedule) {
|
||||||
ScheduleMetadataContent(item, contentWidth, compact)
|
ScheduleMetadataContent(item, contentWidth, compact)
|
||||||
@@ -278,19 +213,74 @@ private fun MetadataContent(
|
|||||||
if (repository.showTitleLogo) repository.logoUrl(item) else null
|
if (repository.showTitleLogo) repository.logoUrl(item) else null
|
||||||
}
|
}
|
||||||
val logo = logoUrl.takeIf { !useTextTitleForLogo(it) }
|
val logo = logoUrl.takeIf { !useTextTitleForLogo(it) }
|
||||||
|
val facts = buildList {
|
||||||
|
item.productionYear?.let { add(it.toString()) }
|
||||||
|
item.runtimeMinutes?.let { add(formatRuntime(it)) }
|
||||||
|
item.officialRating?.takeIf(String::isNotBlank)?.let(::add)
|
||||||
|
}
|
||||||
|
val badges = buildList {
|
||||||
|
airingBadgeLabel(item)?.let(::add)
|
||||||
|
addAll(mediaBadges(item))
|
||||||
|
}
|
||||||
|
val genres = item.genres.take(2).joinToString(ValueSeparator)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.width(contentWidth).fillMaxHeight(),
|
modifier = Modifier.width(contentWidth).fillMaxHeight(),
|
||||||
verticalArrangement = Arrangement.spacedBy(if (compact) 5.dp else 8.dp),
|
verticalArrangement = Arrangement.spacedBy(if (compact) 5.dp else 8.dp),
|
||||||
) {
|
) {
|
||||||
|
metadataHeroContentOrder(contentOrder).forEach { section ->
|
||||||
|
when (section) {
|
||||||
|
MetadataHeroSection.Title -> MetadataHeroTitle(item, logoUrl, logo, compact)
|
||||||
|
MetadataHeroSection.Ratings -> ItemRatingsStrip(
|
||||||
|
item = item, load = true, compact = true, modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
MetadataHeroSection.Facts -> {
|
||||||
|
// Ratings identify their provider in the strip; this line is deliberately factual only.
|
||||||
|
if (facts.isNotEmpty()) {
|
||||||
|
Text(facts.joinToString(FactSeparator), color = MutedText, fontSize = 13.sp,
|
||||||
|
maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
|
}
|
||||||
|
item.membyRecommendationReason?.takeIf(String::isNotBlank)?.let {
|
||||||
|
RecommendationReasonCaption(recommendationReasonSummary(it), Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
|
if (badges.isNotEmpty()) Row(horizontalArrangement = Arrangement.spacedBy(7.dp)) {
|
||||||
|
badges.forEach { MediaBadge(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MetadataHeroSection.Genres -> if (genres.isNotBlank()) Text(
|
||||||
|
genres, color = Color.White, fontSize = 14.sp, fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
MetadataHeroSection.Summary -> Text(
|
||||||
|
item.overview?.takeIf(String::isNotBlank) ?: "No description available.",
|
||||||
|
color = MembyMutedText, fontSize = 14.sp, lineHeight = 18.sp,
|
||||||
|
maxLines = if (compact) 2 else 3, overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MetadataStatus(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object MetadataHeroSection {
|
||||||
|
const val Title = "title"
|
||||||
|
const val Ratings = "ratings"
|
||||||
|
const val Facts = "facts"
|
||||||
|
const val Genres = "genres"
|
||||||
|
const val Summary = "summary"
|
||||||
|
val all = listOf(Title, Ratings, Facts, Genres, Summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun metadataHeroContentOrder(order: List<String>): List<String> =
|
||||||
|
order.filter { it in MetadataHeroSection.all }.distinct()
|
||||||
|
.ifEmpty { MetadataHeroSection.all }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MetadataHeroTitle(item: BaseItem, logoUrl: String?, logo: String?, compact: Boolean) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().then(
|
||||||
.fillMaxWidth()
|
if (logoUrl != null) Modifier.height(MetadataHeroLogoMaxHeight)
|
||||||
.then(
|
else Modifier.heightIn(max = MetadataHeroLogoMaxHeight),
|
||||||
if (logoUrl != null) {
|
|
||||||
Modifier.height(MetadataHeroLogoMaxHeight)
|
|
||||||
} else {
|
|
||||||
Modifier.heightIn(max = MetadataHeroLogoMaxHeight)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.TopStart,
|
contentAlignment = Alignment.TopStart,
|
||||||
) {
|
) {
|
||||||
@@ -299,81 +289,21 @@ private fun MetadataContent(
|
|||||||
logoUrl = logo,
|
logoUrl = logo,
|
||||||
contentDescription = item.seriesName?.takeIf { item.isEpisode } ?: item.name,
|
contentDescription = item.seriesName?.takeIf { item.isEpisode } ?: item.name,
|
||||||
alignment = Alignment.TopStart,
|
alignment = Alignment.TopStart,
|
||||||
modifier = Modifier
|
modifier = Modifier.width(MetadataHeroLogoMaxWidth).height(MetadataHeroLogoMaxHeight),
|
||||||
.width(MetadataHeroLogoMaxWidth)
|
|
||||||
.height(MetadataHeroLogoMaxHeight),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(item.seriesName?.takeIf { item.isEpisode } ?: item.name, color = Color.White,
|
||||||
item.seriesName?.takeIf { item.isEpisode } ?: item.name,
|
fontSize = if (compact) 25.sp else 29.sp, lineHeight = if (compact) 28.sp else 32.sp,
|
||||||
color = Color.White,
|
fontWeight = FontWeight.SemiBold, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||||
fontSize = if (compact) 25.sp else 29.sp,
|
}
|
||||||
lineHeight = if (compact) 28.sp else 32.sp,
|
}
|
||||||
fontWeight = FontWeight.SemiBold,
|
if (item.isEpisode && item.seriesName != null) Text(
|
||||||
maxLines = 2,
|
item.episodeCode
|
||||||
overflow = TextOverflow.Ellipsis,
|
?.filter { it.isLetterOrDigit() }
|
||||||
|
?.lowercase(Locale.ROOT)
|
||||||
|
.orEmpty(),
|
||||||
|
color = MutedText, fontSize = 14.sp, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.isEpisode && item.seriesName != null) {
|
|
||||||
Text(
|
|
||||||
listOfNotNull(item.episodeCode, item.name.takeIf(String::isNotBlank)).joinToString(FactSeparator),
|
|
||||||
color = MutedText,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val facts = buildList {
|
|
||||||
item.productionYear?.let { add(it.toString()) }
|
|
||||||
item.runtimeMinutes?.let { add(formatRuntime(it)) }
|
|
||||||
item.officialRating?.takeIf(String::isNotBlank)?.let(::add)
|
|
||||||
item.genres.take(2).takeIf { it.isNotEmpty() }?.let { add(it.joinToString(ValueSeparator)) }
|
|
||||||
}
|
|
||||||
// No score on this line. Emby's community rating stood beside these facts in gold
|
|
||||||
// with nothing saying whose number it was; the strip below says that for every
|
|
||||||
// score it shows, and two ratings in one panel only invited the comparison.
|
|
||||||
if (facts.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
facts.joinToString(FactSeparator),
|
|
||||||
color = MutedText,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ItemRatingsStrip(
|
|
||||||
item = item,
|
|
||||||
load = true,
|
|
||||||
compact = true,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
val badges = buildList {
|
|
||||||
airingBadgeLabel(item)?.let(::add)
|
|
||||||
addAll(mediaBadges(item))
|
|
||||||
}
|
|
||||||
item.membyRecommendationReason?.takeIf(String::isNotBlank)?.let {
|
|
||||||
RecommendationReasonCaption(
|
|
||||||
text = recommendationReasonSummary(it),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (badges.isNotEmpty()) {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(7.dp)) {
|
|
||||||
badges.forEach { MediaBadge(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
item.overview?.takeIf(String::isNotBlank) ?: "No description available.",
|
|
||||||
color = MembyMutedText,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
lineHeight = 18.sp,
|
|
||||||
maxLines = if (compact) 2 else 3,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
MetadataStatus(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -440,7 +370,7 @@ private fun ScheduleMetadataContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
if (item.isMovieSchedule) "Upcoming digital releases" else "TV this week",
|
if (item.isMovieSchedule) "Upcoming Movies" else "Upcoming TV",
|
||||||
color = QuietText,
|
color = QuietText,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class UserPreferencesTest {
|
|||||||
homeArtworkStyle = "poster",
|
homeArtworkStyle = "poster",
|
||||||
showHomeCardMetadata = false,
|
showHomeCardMetadata = false,
|
||||||
showRatingsStrip = false,
|
showRatingsStrip = false,
|
||||||
|
metadataHeroContentOrder = listOf("title", "ratings", "facts", "summary", "genres"),
|
||||||
hideWatchedMovies = true,
|
hideWatchedMovies = true,
|
||||||
showTitleLogo = false,
|
showTitleLogo = false,
|
||||||
welcomeQuoteStyle = "homicidal",
|
welcomeQuoteStyle = "homicidal",
|
||||||
@@ -142,6 +143,7 @@ class UserPreferencesTest {
|
|||||||
homeArtworkStyle = "backdrop",
|
homeArtworkStyle = "backdrop",
|
||||||
showHomeCardMetadata = false,
|
showHomeCardMetadata = false,
|
||||||
showRatingsStrip = false,
|
showRatingsStrip = false,
|
||||||
|
metadataHeroContentOrder = "title\nratings\nfacts\ngenres\nsummary",
|
||||||
hideWatchedMovies = true,
|
hideWatchedMovies = true,
|
||||||
showTitleLogo = false,
|
showTitleLogo = false,
|
||||||
welcomeQuoteStyle = "positive",
|
welcomeQuoteStyle = "positive",
|
||||||
@@ -162,6 +164,7 @@ class UserPreferencesTest {
|
|||||||
homeArtworkStyle = "backdrop",
|
homeArtworkStyle = "backdrop",
|
||||||
showHomeCardMetadata = false,
|
showHomeCardMetadata = false,
|
||||||
showRatingsStrip = false,
|
showRatingsStrip = false,
|
||||||
|
metadataHeroContentOrder = listOf("title", "ratings", "facts", "genres", "summary"),
|
||||||
hideWatchedMovies = true,
|
hideWatchedMovies = true,
|
||||||
showTitleLogo = false,
|
showTitleLogo = false,
|
||||||
welcomeQuoteStyle = "positive",
|
welcomeQuoteStyle = "positive",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ponzischeme89.memby.ui
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class MetadataHeroOrderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `server order is retained exactly`() {
|
||||||
|
assertEquals(
|
||||||
|
listOf("ratings", "title"),
|
||||||
|
metadataHeroContentOrder(listOf("ratings", "title")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unknown sections fall back to the default layout`() {
|
||||||
|
assertEquals(
|
||||||
|
listOf("title", "ratings", "facts", "genres", "summary"),
|
||||||
|
metadataHeroContentOrder(listOf("unknown")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,6 +130,21 @@ var preferenceCatalogue = []preferenceDefinition{
|
|||||||
Description: "Show third-party ratings on browse and detail pages.",
|
Description: "Show third-party ratings on browse and detail pages.",
|
||||||
Kind: preferenceToggle, Default: true,
|
Kind: preferenceToggle, Default: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// This is operator-owned because the hero is a shared piece of the launcher's
|
||||||
|
// composition, not a per-television preference. The multi value keeps its order.
|
||||||
|
Key: "metadataHeroContentOrder", Name: "Metadata hero content order", Area: "Home layout",
|
||||||
|
Description: "The order of title, ratings, facts, genre and plot summary in the focused metadata hero.",
|
||||||
|
Kind: preferenceMulti, AdminOnly: true,
|
||||||
|
Default: []string{"title", "ratings", "facts", "genres", "summary"},
|
||||||
|
Options: []preferenceOption{
|
||||||
|
option("title", "Movie logo or title"),
|
||||||
|
option("ratings", "Ratings"),
|
||||||
|
option("facts", "Metadata facts"),
|
||||||
|
option("genres", "Genre"),
|
||||||
|
option("summary", "Plot summary"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Key: "hideWatchedMovies", Name: "Hide watched films", Area: "Home",
|
Key: "hideWatchedMovies", Name: "Hide watched films", Area: "Home",
|
||||||
Description: "Keep fully watched films out of browse rows and the hero.",
|
Description: "Keep fully watched films out of browse rows and the hero.",
|
||||||
|
|||||||
Reference in New Issue
Block a user