Revert "translation fixes and modern view focus meta fix"

This commit is contained in:
Nayif 2026-02-25 19:03:09 +05:30 committed by GitHub
parent acf59fb3a1
commit 31992b884c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 98 additions and 1514 deletions

View file

@ -10,7 +10,7 @@ import com.nuvio.tv.domain.model.MetaLink
import com.nuvio.tv.domain.model.PosterShape
import com.nuvio.tv.domain.model.Video
fun MetaDto.toDomain(episodeLabel: String = "Episode"): Meta {
fun MetaDto.toDomain(): Meta {
return Meta(
id = id,
type = ContentType.fromString(type),
@ -39,7 +39,7 @@ fun MetaDto.toDomain(episodeLabel: String = "Episode"): Meta {
photo = castMember.photo?.takeIf { it.isNotBlank() }
)
},
videos = videos?.map { it.toDomain(episodeLabel) } ?: emptyList(),
videos = videos?.map { it.toDomain() } ?: emptyList(),
productionCompanies = emptyList(),
networks = emptyList(),
ageRating = null,
@ -64,10 +64,10 @@ private fun coerceStringList(value: Any?): List<String> {
}
}
fun VideoDto.toDomain(episodeLabel: String = "Episode"): Video {
fun VideoDto.toDomain(): Video {
return Video(
id = id,
title = name ?: title ?: "$episodeLabel ${episode ?: number ?: 0}",
title = name ?: title ?: "Episode ${episode ?: number ?: 0}",
released = released,
thumbnail = thumbnail,
streams = streams?.map { it.toDomain(addonName = "Embedded Streams", addonLogo = null) } ?: emptyList(),

View file

@ -1,6 +1,5 @@
package com.nuvio.tv.data.repository
import android.content.Context
import android.util.Log
import com.nuvio.tv.core.network.NetworkResult
import com.nuvio.tv.core.network.safeApiCall
@ -11,8 +10,6 @@ import com.nuvio.tv.domain.model.Meta
import com.nuvio.tv.domain.model.AddonResource
import com.nuvio.tv.domain.repository.AddonRepository
import com.nuvio.tv.domain.repository.MetaRepository
import com.nuvio.tv.R
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
@ -23,7 +20,6 @@ import javax.inject.Singleton
@Singleton
class MetaRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val api: AddonApi,
private val addonRepository: AddonRepository
) : MetaRepository {
@ -55,8 +51,7 @@ class MetaRepositoryImpl @Inject constructor(
is NetworkResult.Success -> {
val metaDto = result.data.meta
if (metaDto != null) {
val episodeLabel = context.getString(R.string.episodes_episode)
val meta = metaDto.toDomain(episodeLabel)
val meta = metaDto.toDomain()
metaCache[cacheKey] = meta
emit(NetworkResult.Success(meta))
} else {
@ -126,8 +121,7 @@ class MetaRepositoryImpl @Inject constructor(
is NetworkResult.Success -> {
val metaDto = result.data.meta
if (metaDto != null) {
val episodeLabel = context.getString(R.string.episodes_episode)
val meta = metaDto.toDomain(episodeLabel)
val meta = metaDto.toDomain()
addonMetaCache[cacheKey] = meta
metaCache[cacheKey] = meta
emit(NetworkResult.Success(meta))
@ -153,8 +147,7 @@ class MetaRepositoryImpl @Inject constructor(
is NetworkResult.Success -> {
val metaDto = result.data.meta
if (metaDto != null) {
val episodeLabel = context.getString(R.string.episodes_episode)
val meta = metaDto.toDomain(episodeLabel)
val meta = metaDto.toDomain()
addonMetaCache[cacheKey] = meta
metaCache[cacheKey] = meta
Log.d(

View file

@ -118,15 +118,10 @@ fun CatalogRowSection(
Modifier
}
val strTypeMovie = stringResource(R.string.type_movie)
val strTypeSeries = stringResource(R.string.type_series)
val typeLabel = remember(catalogRow.rawType, catalogRow.apiType, strTypeMovie, strTypeSeries) {
val raw = catalogRow.rawType.takeIf { it.isNotBlank() } ?: catalogRow.apiType
when (raw.lowercase()) {
"movie" -> strTypeMovie
"series" -> strTypeSeries
else -> formatAddonTypeLabel(raw)
}
val typeLabel = remember(catalogRow.rawType, catalogRow.apiType) {
formatAddonTypeLabel(
catalogRow.rawType.takeIf { it.isNotBlank() } ?: catalogRow.apiType
)
}
val catalogTitle = remember(catalogRow.catalogName, typeLabel, showCatalogTypeSuffix) {
val formattedName = catalogRow.catalogName.replaceFirstChar { it.uppercase() }

View file

@ -219,9 +219,6 @@ fun ContinueWatchingCard(
val strUpcoming = stringResource(R.string.cw_upcoming)
val strNextUp = stringResource(R.string.cw_next_up)
val strResume = stringResource(R.string.cw_resume)
val strHoursMinLeft = stringResource(R.string.cw_hours_min_left)
val strMinLeft = stringResource(R.string.cw_min_left)
val strAlmostDone = stringResource(R.string.cw_almost_done)
val nextUpBadgeText = nextUp?.let { info ->
if (!info.hasAired) {
info.airDateLabel?.let { strAirsDate } ?: strUpcoming
@ -232,7 +229,7 @@ fun ContinueWatchingCard(
val remainingText = progress?.let {
remember(it.position, it.duration, it.progressPercent) {
when {
it.duration > 0L -> formatRemainingTime(it.remainingTime, strHoursMinLeft, strMinLeft, strAlmostDone)
it.duration > 0L -> formatRemainingTime(it.remainingTime)
it.progressPercent != null -> "${it.progressPercent.toInt().coerceIn(0, 100)}% watched"
else -> strResume
}
@ -501,19 +498,14 @@ private fun firstNonBlank(vararg candidates: String?): String? {
return candidates.firstOrNull { !it.isNullOrBlank() }?.trim()
}
internal fun formatRemainingTime(
remainingMs: Long,
strHoursMinLeft: String,
strMinLeft: String,
strAlmostDone: String
): String {
internal fun formatRemainingTime(remainingMs: Long): String {
val totalMinutes = TimeUnit.MILLISECONDS.toMinutes(remainingMs)
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return when {
hours > 0 -> strHoursMinLeft.format(hours, minutes)
minutes > 0 -> strMinLeft.format(minutes)
else -> strAlmostDone
hours > 0 -> "${hours}h ${minutes}m left"
minutes > 0 -> "${minutes}m left"
else -> "Almost done"
}
}

View file

@ -142,11 +142,11 @@ private fun SyncOverviewCard(overview: SyncOverview) {
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ProfileStatValue(overview.totalAddons, stringResource(R.string.account_stat_addons))
ProfileStatValue(overview.totalPlugins, stringResource(R.string.account_stat_plugins))
ProfileStatValue(overview.totalLibrary, stringResource(R.string.account_stat_library))
ProfileStatValue(overview.totalWatchProgress, stringResource(R.string.account_stat_progress))
ProfileStatValue(overview.totalWatchedItems, stringResource(R.string.account_stat_watched))
ProfileStatValue(overview.totalAddons, "addons")
ProfileStatValue(overview.totalPlugins, "plugins")
ProfileStatValue(overview.totalLibrary, "library")
ProfileStatValue(overview.totalWatchProgress, "progress")
ProfileStatValue(overview.totalWatchedItems, "watched")
}
}
@ -221,11 +221,11 @@ private fun ProfileSyncRow(profile: ProfileSyncStats) {
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ProfileStatValue(profile.addons, stringResource(R.string.account_stat_addons))
ProfileStatValue(profile.plugins, stringResource(R.string.account_stat_plugins))
ProfileStatValue(profile.library, stringResource(R.string.account_stat_library))
ProfileStatValue(profile.watchProgress, stringResource(R.string.account_stat_progress))
ProfileStatValue(profile.watchedItems, stringResource(R.string.account_stat_watched))
ProfileStatValue(profile.addons, "addons")
ProfileStatValue(profile.plugins, "plugins")
ProfileStatValue(profile.library, "library")
ProfileStatValue(profile.watchProgress, "progress")
ProfileStatValue(profile.watchedItems, "watched")
}
}
}

View file

@ -1,11 +1,9 @@
package com.nuvio.tv.ui.screens.account
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nuvio.tv.R
import com.nuvio.tv.BuildConfig
import com.nuvio.tv.core.auth.AuthManager
import com.nuvio.tv.core.plugin.PluginManager
@ -61,8 +59,7 @@ class AccountViewModel @Inject constructor(
private val watchedItemsPreferences: WatchedItemsPreferences,
private val traktAuthDataStore: TraktAuthDataStore,
private val postgrest: Postgrest,
private val profileManager: ProfileManager,
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context
private val profileManager: ProfileManager
) : ViewModel() {
private val _uiState = MutableStateFlow(AccountUiState())
@ -543,9 +540,9 @@ class AccountViewModel @Inject constructor(
_uiState.update {
it.copy(
qrLoginStatus = when (normalizedStatus) {
"approved" -> context.getString(R.string.qr_login_approved)
"pending" -> context.getString(R.string.qr_login_pending)
"expired" -> context.getString(R.string.qr_login_expired)
"approved" -> "Login approved. Finishing sign in..."
"pending" -> "Waiting for approval on your phone..."
"expired" -> "QR login expired. Generate a new code."
else -> "Status: ${result.status}"
},
qrLoginExpiresAtMillis = expiresAtMillis ?: it.qrLoginExpiresAtMillis,

View file

@ -1,8 +1,6 @@
package com.nuvio.tv.ui.screens.detail
import androidx.compose.foundation.background
import androidx.compose.ui.res.stringResource
import com.nuvio.tv.R
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -293,15 +291,9 @@ private fun CastMemberItem(
val character = member.character
if (!character.isNullOrBlank()) {
val displayCharacter = when {
character.equals("Creator", ignoreCase = true) -> stringResource(R.string.cast_role_creator)
character.equals("Director", ignoreCase = true) -> stringResource(R.string.cast_role_director)
character.equals("Writer", ignoreCase = true) -> stringResource(R.string.cast_role_writer)
else -> character
}
Spacer(modifier = Modifier.height(4.dp))
Text(
text = displayCharacter,
text = character,
style = MaterialTheme.typography.labelSmall,
color = NuvioColors.TextTertiary,
maxLines = 1,

View file

@ -5,18 +5,17 @@ import java.util.Locale
import java.util.TimeZone
fun formatReleaseDate(isoDate: String): String {
val locale = Locale.getDefault()
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
val outputFormat = SimpleDateFormat("MMMM d, yyyy", locale)
val outputFormat = SimpleDateFormat("MMMM d, yyyy", Locale.US)
val date = inputFormat.parse(isoDate)
date?.let { outputFormat.format(it) } ?: ""
} catch (e: Exception) {
try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
val outputFormat = SimpleDateFormat("MMMM d, yyyy", locale)
val outputFormat = SimpleDateFormat("MMMM d, yyyy", Locale.US)
val date = inputFormat.parse(isoDate)
date?.let { outputFormat.format(it) } ?: ""
} catch (e: Exception) {

View file

@ -39,16 +39,12 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import android.content.Context
import com.nuvio.tv.R
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
private const val TAG = "MetaDetailsViewModel"
@HiltViewModel
class MetaDetailsViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val metaRepository: MetaRepository,
private val tmdbSettingsDataStore: TmdbSettingsDataStore,
private val tmdbService: TmdbService,
@ -747,7 +743,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = meta.id,
nextSeason = null,
nextEpisode = null,
displayText = context.getString(R.string.detail_btn_resume)
displayText = "Resume"
)
} else {
NextToWatch(
@ -756,7 +752,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = meta.id,
nextSeason = null,
nextEpisode = null,
displayText = context.getString(R.string.detail_btn_play)
displayText = "Play"
)
}
updateNextToWatch(nextToWatch)
@ -775,7 +771,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = meta.id,
nextSeason = null,
nextEpisode = null,
displayText = context.getString(R.string.detail_btn_play)
displayText = "Play"
)
)
return@launch
@ -809,7 +805,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = metaId,
nextSeason = null,
nextEpisode = null,
displayText = context.getString(R.string.detail_btn_play)
displayText = "Play"
)
}
@ -826,7 +822,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = matchedEpisode?.id ?: latestProgress.videoId,
nextSeason = season,
nextEpisode = episode,
displayText = context.getString(R.string.detail_btn_resume_episode, season, episode)
displayText = "Resume S${season}E${episode}"
)
}
@ -839,7 +835,7 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = next.id,
nextSeason = next.season,
nextEpisode = next.episode,
displayText = context.getString(R.string.detail_btn_next_episode, next.season, next.episode)
displayText = "Next S${next.season}E${next.episode}"
)
}
}
@ -880,24 +876,19 @@ class MetaDetailsViewModel @Inject constructor(
nextVideoId = resumeEpisode.id,
nextSeason = resumeEpisode.season,
nextEpisode = resumeEpisode.episode,
displayText = context.getString(R.string.detail_btn_resume_episode, resumeEpisode.season, resumeEpisode.episode)
displayText = "Resume S${resumeEpisode.season}E${resumeEpisode.episode}"
)
}
nextUnwatchedEpisode != null -> {
val hasWatchedSomething = fallbackProgressMap.isNotEmpty()
val s = nextUnwatchedEpisode.season
val e = nextUnwatchedEpisode.episode
val displayPrefix = if (hasWatchedSomething) "Next" else "Play"
NextToWatch(
watchProgress = null,
isResume = false,
nextVideoId = nextUnwatchedEpisode.id,
nextSeason = s,
nextEpisode = e,
displayText = if (hasWatchedSomething) {
context.getString(R.string.detail_btn_next_episode, s, e)
} else {
context.getString(R.string.detail_btn_play_episode, s, e)
}
nextSeason = nextUnwatchedEpisode.season,
nextEpisode = nextUnwatchedEpisode.episode,
displayText = "$displayPrefix S${nextUnwatchedEpisode.season}E${nextUnwatchedEpisode.episode}"
)
}
else -> {
@ -909,9 +900,9 @@ class MetaDetailsViewModel @Inject constructor(
nextSeason = firstEpisode?.season,
nextEpisode = firstEpisode?.episode,
displayText = if (firstEpisode != null) {
context.getString(R.string.detail_btn_play_episode, firstEpisode.season, firstEpisode.episode)
"Play S${firstEpisode.season}E${firstEpisode.episode}"
} else {
context.getString(R.string.detail_btn_play)
"Play"
}
)
}

View file

@ -219,7 +219,6 @@ internal fun ModernRowSection(
onContinueWatchingOptions: (ContinueWatchingItem) -> Unit,
isCatalogItemWatched: (MetaPreview) -> Boolean,
onCatalogItemLongPress: (MetaPreview, String) -> Unit,
onItemFocus: (MetaPreview) -> Unit,
onCatalogSelectionFocused: (FocusedCatalogSelection) -> Unit,
onNavigateToDetail: (String, String, String) -> Unit,
onLoadMoreCatalog: (String, String, String) -> Unit,
@ -391,7 +390,6 @@ internal fun ModernRowSection(
val isContinueWatchingRow = row.key == "continue_watching"
val onFocused = {
onRowItemFocused(row.key, index, isContinueWatchingRow)
item.metaPreview?.let { onItemFocus(it) }
}
when (val payload = item.payload) {

View file

@ -76,10 +76,8 @@ import androidx.compose.ui.res.stringResource
import com.nuvio.tv.R
@Composable
private fun localizedTypeLabel(key: String): String = when (key.lowercase()) {
private fun localizedTypeLabel(key: String): String = when (key) {
LibraryTypeTab.ALL_KEY -> stringResource(R.string.library_type_all)
"movie" -> stringResource(R.string.type_movie)
"series" -> stringResource(R.string.type_series)
else -> formatAddonTypeLabel(key)
}

View file

@ -126,7 +126,7 @@ internal fun EpisodesSidePanel(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (uiState.showEpisodeStreams) stringResource(R.string.episodes_panel_streams_title) else stringResource(R.string.episodes_panel_title),
text = if (uiState.showEpisodeStreams) "Streams" else "Episodes",
style = MaterialTheme.typography.headlineSmall,
color = NuvioColors.TextPrimary
)

View file

@ -1,6 +1,6 @@
import android.content.Context
package com.nuvio.tv.ui.screens.player
import androidx.media3.ui.AspectRatioFrameLayout
import com.nuvio.tv.R
internal object PlayerDisplayModeUtils {
fun nextResizeMode(currentMode: Int): Int {
@ -14,14 +14,14 @@ internal object PlayerDisplayModeUtils {
}
}
fun resizeModeLabel(mode: Int, context: Context): String {
fun resizeModeLabel(mode: Int): String {
return when (mode) {
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.player_aspect_fit)
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.player_aspect_stretch)
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH -> context.getString(R.string.player_aspect_fit_width)
AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT -> context.getString(R.string.player_aspect_fit_height)
AspectRatioFrameLayout.RESIZE_MODE_ZOOM -> context.getString(R.string.player_aspect_crop)
else -> context.getString(R.string.player_aspect_fit)
AspectRatioFrameLayout.RESIZE_MODE_FIT -> "Fit (Original)"
AspectRatioFrameLayout.RESIZE_MODE_FILL -> "Stretch"
AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH -> "Fit Width"
AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT -> "Fit Height"
AspectRatioFrameLayout.RESIZE_MODE_ZOOM -> "Crop"
else -> "Fit (Original)"
}
}
}

View file

@ -711,7 +711,7 @@ fun PlayerRuntimeController.onEvent(event: PlayerEvent) {
PlayerEvent.OnToggleAspectRatio -> {
val currentMode = _uiState.value.resizeMode
val newMode = PlayerDisplayModeUtils.nextResizeMode(currentMode)
val modeText = PlayerDisplayModeUtils.resizeModeLabel(newMode, context)
val modeText = PlayerDisplayModeUtils.resizeModeLabel(newMode)
Log.d("PlayerViewModel", "Aspect ratio toggled: $currentMode -> $newMode")
_uiState.update {
it.copy(

View file

@ -1770,7 +1770,7 @@ private fun SpeedItem(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (speed == 1f) stringResource(R.string.player_speed_normal) else "${speed}x",
text = if (speed == 1f) "Normal" else "${speed}x",
style = MaterialTheme.typography.bodyLarge,
color = if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary
)

View file

@ -103,19 +103,12 @@ internal fun SubtitleSelectionDialog(
.distinct()
.size
}
val strAddons = stringResource(R.string.subtitle_tab_addons)
val strLanguages = stringResource(R.string.subtitle_tab_languages)
val addonsTabTitle = when (subtitleOrganizationMode) {
SubtitleOrganizationMode.BY_LANGUAGE -> strLanguages
SubtitleOrganizationMode.BY_LANGUAGE -> "Languages"
SubtitleOrganizationMode.NONE,
SubtitleOrganizationMode.BY_ADDON -> strAddons
SubtitleOrganizationMode.BY_ADDON -> "Addons"
}
val tabs = listOf(
stringResource(R.string.subtitle_tab_builtin),
addonsTabTitle,
stringResource(R.string.subtitle_tab_style),
stringResource(R.string.subtitle_tab_delay)
)
val tabs = listOf("Built-in", addonsTabTitle, "Style", "Delay")
val tabFocusRequesters = remember { tabs.map { FocusRequester() } }
Dialog(onDismissRequest = onDismiss) {
@ -275,7 +268,7 @@ private fun InternalSubtitlesContent(
) {
item {
TrackItem(
track = TrackInfo(index = -1, name = stringResource(R.string.subtitle_none), language = null),
track = TrackInfo(index = -1, name = "None", language = null),
isSelected = selectedIndex == -1 && selectedAddonSubtitle == null,
onClick = onDisableSubtitles
)
@ -317,17 +310,11 @@ private fun AddonSubtitlesContent(
isLoading: Boolean,
onSubtitleSelected: (Subtitle) -> Unit
) {
val strAll = stringResource(R.string.subtitle_all)
val strLanguages = stringResource(R.string.subtitle_tab_languages)
val strAddons = stringResource(R.string.subtitle_tab_addons)
val viewData = remember(subtitles, preferredLanguage, subtitleOrganizationMode, strAll, strLanguages, strAddons) {
val viewData = remember(subtitles, preferredLanguage, subtitleOrganizationMode) {
buildAddonSubtitleViewData(
subtitles = subtitles,
preferredLanguage = preferredLanguage,
mode = subtitleOrganizationMode,
strAll = strAll,
strLanguages = strLanguages,
strAddons = strAddons
mode = subtitleOrganizationMode
)
}
@ -491,10 +478,7 @@ private data class AddonSubtitleViewData(
private fun buildAddonSubtitleViewData(
subtitles: List<Subtitle>,
preferredLanguage: String,
mode: SubtitleOrganizationMode,
strAll: String,
strLanguages: String,
strAddons: String
mode: SubtitleOrganizationMode
): AddonSubtitleViewData {
val preferredFirst = sortByPreferredLanguage(subtitles, preferredLanguage) { it.lang }
@ -505,7 +489,7 @@ private fun buildAddonSubtitleViewData(
filters = listOf(
SubtitleFilter(
key = "all",
label = strAll,
label = "All",
items = preferredFirst
)
)
@ -514,7 +498,7 @@ private fun buildAddonSubtitleViewData(
SubtitleOrganizationMode.BY_LANGUAGE -> {
AddonSubtitleViewData(
filterTitle = strLanguages,
filterTitle = "Languages",
filters = preferredFirst
.groupBy { subtitleLanguageGroupKey(it.lang) }
.map { (languageKey, items) ->
@ -530,7 +514,7 @@ private fun buildAddonSubtitleViewData(
SubtitleOrganizationMode.BY_ADDON -> {
AddonSubtitleViewData(
filterTitle = strAddons,
filterTitle = "Addons",
filters = preferredFirst
.groupBy { it.addonName }
.map { (addon, items) ->
@ -695,11 +679,11 @@ private fun SubtitleStyleContent(
// Core section
item {
StyleSection(
title = stringResource(R.string.subtitle_section_core),
title = "Core",
icon = Icons.Default.FormatSize
) {
// Font Size
StyleSettingRow(label = stringResource(R.string.subtitle_font_size)) {
StyleSettingRow(label = "Font Size") {
StyleStepperButton(
icon = Icons.Default.Remove,
onClick = { onEvent(PlayerEvent.OnSetSubtitleSize(subtitleStyle.size - 10)) }
@ -714,7 +698,7 @@ private fun SubtitleStyleContent(
Spacer(modifier = Modifier.height(12.dp))
// Bold
StyleSettingRow(label = stringResource(R.string.subtitle_bold)) {
StyleSettingRow(label = "Bold") {
StyleToggleButton(
isEnabled = subtitleStyle.bold,
onClick = { onEvent(PlayerEvent.OnSetSubtitleBold(!subtitleStyle.bold)) }
@ -726,7 +710,7 @@ private fun SubtitleStyleContent(
// Advanced section
item {
StyleSection(
title = stringResource(R.string.subtitle_section_advanced),
title = "Advanced",
icon = Icons.Default.Tune
) {
// Text Color
@ -751,7 +735,7 @@ private fun SubtitleStyleContent(
Spacer(modifier = Modifier.height(12.dp))
// Outline
StyleSettingRow(label = stringResource(R.string.subtitle_outline)) {
StyleSettingRow(label = "Outline") {
StyleToggleButton(
isEnabled = subtitleStyle.outlineEnabled,
onClick = { onEvent(PlayerEvent.OnSetSubtitleOutlineEnabled(!subtitleStyle.outlineEnabled)) }
@ -784,7 +768,7 @@ private fun SubtitleStyleContent(
Spacer(modifier = Modifier.height(12.dp))
// Bottom Offset
StyleSettingRow(label = stringResource(R.string.subtitle_bottom_offset)) {
StyleSettingRow(label = "Bottom Offset") {
StyleStepperButton(
icon = Icons.Default.Remove,
onClick = { onEvent(PlayerEvent.OnSetSubtitleVerticalOffset(subtitleStyle.verticalOffset - 5)) }
@ -968,7 +952,7 @@ private fun StyleToggleButton(
shape = CardDefaults.shape(RoundedCornerShape(10.dp))
) {
Text(
text = if (isEnabled) stringResource(R.string.subtitle_on) else stringResource(R.string.subtitle_off),
text = if (isEnabled) "On" else "Off",
style = MaterialTheme.typography.bodyMedium,
color = if (isEnabled) Color.White else Color.White.copy(alpha = 0.5f),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)

View file

@ -85,18 +85,10 @@ internal fun DiscoverSection(
val genres = selectedCatalog?.genres.orEmpty()
var expandedPicker by remember { mutableStateOf<String?>(null) }
val strTypeMovie = stringResource(R.string.type_movie)
val strTypeSeries = stringResource(R.string.type_series)
fun localizedTypeLabel(type: String): String = when (type.lowercase()) {
"movie" -> strTypeMovie
"series" -> strTypeSeries
else -> formatAddonTypeLabel(type)
}
val availableTypes = remember(uiState.discoverCatalogs) {
uiState.discoverCatalogs.map { it.type }.distinct()
}
val selectedTypeLabel = localizedTypeLabel(uiState.selectedDiscoverType)
val selectedTypeLabel = formatAddonTypeLabel(uiState.selectedDiscoverType)
val selectedCatalogLabel = selectedCatalog?.catalogName ?: stringResource(R.string.discover_select_catalog)
val selectedGenreLabel = uiState.selectedDiscoverGenre ?: stringResource(R.string.discover_genre_default)
@ -122,7 +114,7 @@ internal fun DiscoverSection(
value = selectedTypeLabel,
expanded = expandedPicker == "type",
options = availableTypes.map { type ->
val label = localizedTypeLabel(type)
val label = formatAddonTypeLabel(type)
DiscoverOption(label, type)
},
onExpandedChange = { shouldExpand ->
@ -172,7 +164,7 @@ internal fun DiscoverSection(
val metadataSegments = buildList {
add(catalog.addonName)
if (uiState.catalogTypeSuffixEnabled) {
localizedTypeLabel(catalog.type)
formatAddonTypeLabel(catalog.type)
.takeIf { it.isNotEmpty() }
?.let(::add)
}

View file

@ -328,8 +328,8 @@ fun SearchScreen(
contentAlignment = Alignment.Center
) {
EmptyScreenState(
title = stringResource(R.string.search_start_title),
subtitle = stringResource(R.string.search_start_subtitle),
title = "Start Searching",
subtitle = "Enter at least 2 characters",
icon = Icons.Default.Search
)
}
@ -396,11 +396,11 @@ fun SearchScreen(
trimmedSubmittedQuery.length < 2 && !hasPendingUnsubmittedQuery -> {
item {
EmptyScreenState(
title = stringResource(R.string.search_start_title),
title = "Start Searching",
subtitle = if (uiState.discoverEnabled) {
stringResource(R.string.search_start_subtitle)
"Enter at least 2 characters"
} else {
stringResource(R.string.search_start_subtitle_no_discover)
"Discover is disabled. Enter at least 2 characters"
},
icon = Icons.Default.Search
)

View file

@ -230,7 +230,7 @@ internal fun PlaybackSettingsSections(
item(key = "general_osd_clock") {
ToggleSettingsItem(
icon = Icons.Default.Timer,
title = stringResource(R.string.playback_osd_clock),
title = "OSD Clock",
subtitle = stringResource(R.string.playback_show_clock_sub),
isChecked = playerSettings.osdClockEnabled,
onCheckedChange = onSetOsdClockEnabled,

View file

@ -1,9 +1,7 @@
package com.nuvio.tv.ui.screens.settings
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nuvio.tv.R
import com.nuvio.tv.data.local.TraktAuthDataStore
import com.nuvio.tv.data.local.TraktAuthState
import com.nuvio.tv.data.local.TraktSettingsDataStore
@ -52,8 +50,7 @@ class TraktViewModel @Inject constructor(
private val traktAuthService: TraktAuthService,
private val traktAuthDataStore: TraktAuthDataStore,
private val traktProgressService: TraktProgressService,
private val traktSettingsDataStore: TraktSettingsDataStore,
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context
private val traktSettingsDataStore: TraktSettingsDataStore
) : ViewModel() {
private val _uiState = MutableStateFlow(TraktUiState())
val uiState: StateFlow<TraktUiState> = _uiState.asStateFlow()
@ -314,7 +311,7 @@ class TraktViewModel @Inject constructor(
_uiState.update {
it.copy(
isPolling = true,
statusMessage = context.getString(R.string.trakt_waiting_approval)
statusMessage = "Waiting for approval..."
)
}
}

View file

@ -15,9 +15,6 @@
<string name="cw_upcoming">Nadchodzące</string>
<string name="cw_next_up">Następny odcinek</string>
<string name="cw_resume">Wznów</string>
<string name="cw_hours_min_left">Zostało %1$dg %2$dm</string>
<string name="cw_min_left">Zostało %1$dm</string>
<string name="cw_almost_done">Prawie koniec</string>
<string name="cw_airs_date">Emisja %1$s</string>
<string name="cw_action_go_to_details">Przejdź do szczegółów</string>
<string name="cw_action_remove">Usuń</string>
@ -56,15 +53,7 @@
<string name="episodes_season_actions">Akcje sezonu</string>
<!-- MetaDetailsScreen -->
<string name="detail_btn_play">Odtwórz</string>
<string name="detail_btn_resume">Wznów</string>
<string name="detail_btn_play_episode">Odtwórz S%1$dE%2$d</string>
<string name="detail_btn_resume_episode">Wznów S%1$dE%2$d</string>
<string name="detail_btn_next_episode">Następny S%1$dE%2$d</string>
<string name="detail_tab_cast">Twórcy i obsada</string>
<string name="cast_role_creator">Twórca</string>
<string name="cast_role_director">Reżyser</string>
<string name="cast_role_writer">Scenarzysta</string>
<string name="detail_tab_ratings">Oceny</string>
<string name="detail_tab_more_like_this">Podobne</string>
<string name="detail_section_network">Stacja</string>
@ -114,11 +103,6 @@
<string name="settings_debug_subtitle">Narzędzia deweloperskie i flagi funkcji.</string>
<string name="settings_plugins_section_subtitle">Zarządzaj repozytoriami, dostawcami i stanami wtyczek.</string>
<string name="settings_account_section_subtitle">Status konta i synchronizacji.</string>
<string name="account_stat_addons">dodatki</string>
<string name="account_stat_plugins">wtyczki</string>
<string name="account_stat_library">biblioteka</string>
<string name="account_stat_progress">postęp</string>
<string name="account_stat_watched">obejrzane</string>
<string name="settings_integrations_section">Integracje</string>
<string name="settings_integrations_section_subtitle">Wybierz ustawienia TMDB lub MDBList</string>
<string name="settings_tmdb_subtitle">Kontrolki wzbogacania metadanych</string>
@ -135,14 +119,13 @@
<!-- PlaybackSettingsSections -->
<string name="playback_section_general">Ogólne</string>
<string name="playback_section_general_desc">Podstawowe zachowanie odtwarzacza.</string>
<string name="playback_loading_overlay">Ekan ładowania</string>
<string name="playback_loading_overlay">Nakładka ładowania</string>
<string name="playback_loading_overlay_sub">Pokaż ekran ładowania do pojawienia się pierwszej klatki.</string>
<string name="playback_pause_overlay">Ekran pauzy</string>
<string name="playback_pause_overlay_sub">Pokaż ekran ze szczegółami po 5 sekundach pauzy.</string>
<string name="playback_pause_overlay">Nakładka pauzy</string>
<string name="playback_pause_overlay_sub">Pokaż nakładkę ze szczegółami po 5 sekundach pauzy.</string>
<string name="playback_show_clock_sub">Pokaż aktualny czas i czas zakończenia podczas wyświetlania kontrolek.</string>
<string name="playback_osd_clock">Zegar OSD</string>
<string name="playback_skip_intro">Pomiń intro</string>
<string name="playback_skip_intro_sub">Używaj introdb.app do wykrywania intro i podsumowań.</string>
<string name="playback_skip_intro_sub">Używaj introdb.app do wykrywania intro i recap.</string>
<string name="playback_auto_frame_rate">Auto częstotliwość odświeżania</string>
<string name="playback_section_player">Odtwarzacz i wybór źródła</string>
<string name="playback_section_player_desc">Preferencje odtwarzacza, auto-play i filtrowanie źródeł.</string>
@ -237,7 +220,7 @@
<string name="autoplay_next_episode_sub">Automatycznie rozpocznij następny odcinek gdy pojawi się monit.</string>
<string name="autoplay_threshold_pct">Procent</string>
<string name="autoplay_threshold_min">Minut przed końcem</string>
<string name="autoplay_threshold_mode">Określanie progu następnego odcinka</string>
<string name="autoplay_threshold_mode">Tryb progu następnego odcinka</string>
<string name="autoplay_threshold_pct_title">Próg procentowy</string>
<string name="autoplay_threshold_pct_sub">Zapasowy gdy brak znacznika outro.</string>
<string name="autoplay_threshold_min_title">Próg minutowy</string>
@ -308,15 +291,15 @@
<string name="layout_trailer_button_sub">Pokaż przycisk zwiastuna na stronie szczegółów (tylko gdy zwiastun jest dostępny).</string>
<string name="layout_prefer_external_meta">Preferuj metadane z zewnętrznego dodatku</string>
<string name="layout_prefer_external_meta_sub">Używaj metadanych z zewnętrznego dodatku zamiast dodatku katalogowego.</string>
<string name="layout_section_focused">Zaznaczony plakat</string>
<string name="layout_section_focused_desc">Zaawansowane zachowanie zaznaczonych kart plakatów.</string>
<string name="layout_expand_poster">Rozwiń zaznaczony plakat do tła</string>
<string name="layout_expand_poster_sub">Rozwiń zaznaczony plakat po opóźnieniu bezczynności.</string>
<string name="layout_section_focused">Skupiony plakat</string>
<string name="layout_section_focused_desc">Zaawansowane zachowanie skupionych kart plakatów.</string>
<string name="layout_expand_poster">Rozwiń skupiony plakat do tła</string>
<string name="layout_expand_poster_sub">Rozwiń skupiony plakat po opóźnieniu bezczynności.</string>
<string name="layout_expand_delay">Opóźnienie rozwinięcia tła</string>
<string name="layout_expand_delay_sub">Jak długo czekać przed rozwinięciem zaznaczonych kart.</string>
<string name="layout_expand_delay_sub">Jak długo czekać przed rozwinięciem skupionych kart.</string>
<string name="layout_autoplay_trailer">Auto-odtwarzanie zwiastuna</string>
<string name="layout_autoplay_trailer_expanded">Auto-odtwarzanie zwiastuna w rozszerzonej karcie</string>
<string name="layout_autoplay_trailer_sub">Odtwarzaj podgląd zwiastuna dla zaznaczonej treści gdy dostępny.</string>
<string name="layout_autoplay_trailer_sub">Odtwarzaj podgląd zwiastuna dla skupionej treści gdy dostępny.</string>
<string name="layout_autoplay_trailer_expanded_sub">Odtwarzaj zwiastun w rozszerzonym tle gdy dostępny.</string>
<string name="layout_trailer_muted">Odtwarzaj zwiastun bez dźwięku</string>
<string name="layout_trailer_muted_sub_preview">Wycisz audio zwiastuna podczas podglądu auto-play.</string>
@ -403,10 +386,6 @@
<string name="trakt_connected_as">Połączono jako %1$s</string>
<string name="trakt_account_login">Logowanie do konta</string>
<string name="trakt_awaiting_instruction">Przejdź na trakt.tv/activate i wprowadź ten kod:</string>
<string name="trakt_waiting_approval">Oczekiwanie na zatwierdzenie…</string>
<string name="qr_login_approved">Logowanie zatwierdzone. Trwa kończenie…</string>
<string name="qr_login_pending">Oczekiwanie na zatwierdzenie na telefonie…</string>
<string name="qr_login_expired">Kod QR wygasł. Wygeneruj nowy.</string>
<string name="trakt_code_expires">Kod wygasa za %1$s</string>
<string name="trakt_token_refreshes">Token dostępu Trakt odświeży się za %1$s</string>
<string name="trakt_login_instruction">Naciśnij Zaloguj, aby rozpocząć uwierzytelnianie urządzenia Trakt. Tutaj pojawi się kod QR.</string>
@ -552,14 +531,6 @@
<string name="episodes_panel_reload">Odśwież</string>
<string name="episodes_panel_no_streams">Nie znaleziono źródeł</string>
<string name="episodes_panel_no_episodes">Brak dostępnych odcinków</string>
<string name="episodes_panel_title">Odcinki</string>
<string name="episodes_panel_streams_title">Strumienie</string>
<string name="player_speed_normal">Normalna</string>
<string name="player_aspect_fit">Dopasuj (oryginalny)</string>
<string name="player_aspect_stretch">Rozciągnij</string>
<string name="player_aspect_fit_width">Dopasuj szerokość</string>
<string name="player_aspect_fit_height">Dopasuj wysokość</string>
<string name="player_aspect_crop">Przytnij</string>
<!-- AudioDialog -->
<string name="audio_dialog_title">Dźwięk</string>
@ -572,21 +543,6 @@
<string name="subtitle_text_color">Kolor tekstu</string>
<string name="subtitle_outline_color">Kolor obrysu</string>
<string name="subtitle_reset_defaults">Przywróć domyślne</string>
<string name="subtitle_tab_builtin">Wbudowane</string>
<string name="subtitle_tab_addons">Dodatki</string>
<string name="subtitle_tab_languages">Języki</string>
<string name="subtitle_tab_style">Styl</string>
<string name="subtitle_tab_delay">Opóźnienie</string>
<string name="subtitle_none">Brak</string>
<string name="subtitle_all">Wszystkie</string>
<string name="subtitle_section_core">Podstawowe</string>
<string name="subtitle_section_advanced">Zaawansowane</string>
<string name="subtitle_font_size">Rozmiar czcionki</string>
<string name="subtitle_bold">Pogrubienie</string>
<string name="subtitle_outline">Obramowanie</string>
<string name="subtitle_bottom_offset">Przesunięcie od dołu</string>
<string name="subtitle_on">Wł.</string>
<string name="subtitle_off">Wył.</string>
<string name="subtitle_style_title">Styl napisów</string>
<string name="subtitle_style_color">Kolor</string>
<string name="subtitle_style_reset">Resetuj</string>
@ -604,9 +560,6 @@
<!-- SearchScreen -->
<string name="search_keyboard_hint">Naciśnij Gotowe na klawiaturze, aby wyszukać</string>
<string name="search_placeholder">Szukaj filmów i seriali</string>
<string name="search_start_title">Zacznij szukać</string>
<string name="search_start_subtitle">Wpisz co najmniej 2 znaki</string>
<string name="search_start_subtitle_no_discover">Odkrywanie jest wyłączone. Wpisz co najmniej 2 znaki</string>
<!-- LibraryScreen -->
<string name="library_syncing">Synchronizowanie biblioteki Trakt\u2026</string>

View file

@ -16,9 +16,6 @@
<string name="cw_upcoming">Upcoming</string>
<string name="cw_next_up">Next Up</string>
<string name="cw_resume">Resume</string>
<string name="cw_hours_min_left">%1$dh %2$dm left</string>
<string name="cw_min_left">%1$dm left</string>
<string name="cw_almost_done">Almost done</string>
<string name="cw_airs_date">Airs %1$s</string>
<string name="cw_action_go_to_details">Go to details</string>
<string name="cw_action_remove">Remove</string>
@ -58,15 +55,7 @@
<string name="episodes_season_actions">Season actions</string>
<!-- MetaDetailsScreen -->
<string name="detail_btn_play">Play</string>
<string name="detail_btn_resume">Resume</string>
<string name="detail_btn_play_episode">Play S%1$dE%2$d</string>
<string name="detail_btn_resume_episode">Resume S%1$dE%2$d</string>
<string name="detail_btn_next_episode">Next S%1$dE%2$d</string>
<string name="detail_tab_cast">Creator and Cast</string>
<string name="cast_role_creator">Creator</string>
<string name="cast_role_director">Director</string>
<string name="cast_role_writer">Writer</string>
<string name="detail_tab_ratings">Ratings</string>
<string name="detail_tab_more_like_this">More like this</string>
<string name="detail_section_network">Network</string>
@ -116,11 +105,6 @@
<string name="settings_debug_subtitle">Developer tools and feature flags.</string>
<string name="settings_plugins_section_subtitle">Manage repositories, providers, and plugin states.</string>
<string name="settings_account_section_subtitle">Account and sync status.</string>
<string name="account_stat_addons">addons</string>
<string name="account_stat_plugins">plugins</string>
<string name="account_stat_library">library</string>
<string name="account_stat_progress">progress</string>
<string name="account_stat_watched">watched</string>
<string name="settings_integrations_section">Integrations</string>
<string name="settings_integrations_section_subtitle">Choose TMDB or MDBList settings</string>
<string name="settings_tmdb_subtitle">Metadata enrichment controls</string>
@ -142,7 +126,6 @@
<string name="playback_pause_overlay">Pause Overlay</string>
<string name="playback_pause_overlay_sub">Show details overlay after 5 seconds while paused.</string>
<string name="playback_show_clock_sub">Show current time and end time while controls are visible.</string>
<string name="playback_osd_clock">OSD Clock</string>
<string name="playback_skip_intro">Skip Intro</string>
<string name="playback_skip_intro_sub">Use introdb.app to detect intros and recaps.</string>
<string name="playback_auto_frame_rate">Auto Frame Rate</string>
@ -405,10 +388,6 @@
<string name="trakt_connected_as">Connected as %1$s</string>
<string name="trakt_account_login">Account Login</string>
<string name="trakt_awaiting_instruction">Go to trakt.tv/activate and enter this code:</string>
<string name="trakt_waiting_approval">Waiting for approval…</string>
<string name="qr_login_approved">Login approved. Finishing sign in…</string>
<string name="qr_login_pending">Waiting for approval on your phone…</string>
<string name="qr_login_expired">QR login expired. Generate a new code.</string>
<string name="trakt_code_expires">Code expires in %1$s</string>
<string name="trakt_token_refreshes">Trakt access token refreshes in %1$s</string>
<string name="trakt_login_instruction">Press Login to start Trakt device authentication. A QR code will appear here.</string>
@ -555,14 +534,6 @@
<string name="episodes_panel_reload">Reload</string>
<string name="episodes_panel_no_streams">No streams found</string>
<string name="episodes_panel_no_episodes">No episodes available</string>
<string name="episodes_panel_title">Episodes</string>
<string name="episodes_panel_streams_title">Streams</string>
<string name="player_speed_normal">Normal</string>
<string name="player_aspect_fit">Fit (Original)</string>
<string name="player_aspect_stretch">Stretch</string>
<string name="player_aspect_fit_width">Fit Width</string>
<string name="player_aspect_fit_height">Fit Height</string>
<string name="player_aspect_crop">Crop</string>
<!-- AudioDialog -->
<string name="audio_dialog_title">Audio</string>
@ -575,21 +546,6 @@
<string name="subtitle_text_color">Text Color</string>
<string name="subtitle_outline_color">Outline Color</string>
<string name="subtitle_reset_defaults">Reset Defaults</string>
<string name="subtitle_tab_builtin">Built-in</string>
<string name="subtitle_tab_addons">Addons</string>
<string name="subtitle_tab_languages">Languages</string>
<string name="subtitle_tab_style">Style</string>
<string name="subtitle_tab_delay">Delay</string>
<string name="subtitle_none">None</string>
<string name="subtitle_all">All</string>
<string name="subtitle_section_core">Core</string>
<string name="subtitle_section_advanced">Advanced</string>
<string name="subtitle_font_size">Font Size</string>
<string name="subtitle_bold">Bold</string>
<string name="subtitle_outline">Outline</string>
<string name="subtitle_bottom_offset">Bottom Offset</string>
<string name="subtitle_on">On</string>
<string name="subtitle_off">Off</string>
<string name="subtitle_style_title">Subtitle Style</string>
<string name="subtitle_style_color">Color</string>
<string name="subtitle_style_reset">Reset</string>
@ -607,9 +563,6 @@
<!-- SearchScreen -->
<string name="search_keyboard_hint">Press Done on the keyboard to search</string>
<string name="search_placeholder">Search movies &amp; series</string>
<string name="search_start_title">Start Searching</string>
<string name="search_start_subtitle">Enter at least 2 characters</string>
<string name="search_start_subtitle_no_discover">Discover is disabled. Enter at least 2 characters</string>
<!-- LibraryScreen -->
<string name="library_syncing">Syncing Trakt library\u2026</string>