mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Extract strings into resources (#151)
Move all strings into resource files to make future translating easier No user facing changes in this PR
This commit is contained in:
parent
40267be633
commit
2840fa3f2a
52 changed files with 616 additions and 306 deletions
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.provider.Settings
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.github.damontecres.wholphin.BuildConfig
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -47,9 +48,11 @@ annotation class IoCoroutineScope
|
|||
object AppModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun clientInfo(): ClientInfo =
|
||||
fun clientInfo(
|
||||
@ApplicationContext context: Context,
|
||||
): ClientInfo =
|
||||
ClientInfo(
|
||||
name = "Wholphin",
|
||||
name = context.getString(R.string.app_name),
|
||||
version = BuildConfig.VERSION_NAME,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,11 @@ sealed interface AppPreference<T> {
|
|||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
"$value seconds"
|
||||
WholphinApplication.instance.resources.getQuantityString(
|
||||
R.plurals.seconds,
|
||||
value.toInt(),
|
||||
value.toInt(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -95,7 +99,11 @@ sealed interface AppPreference<T> {
|
|||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
"$value seconds"
|
||||
WholphinApplication.instance.resources.getQuantityString(
|
||||
R.plurals.seconds,
|
||||
value.toInt(),
|
||||
value.toInt(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -137,7 +145,14 @@ sealed interface AppPreference<T> {
|
|||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { controllerTimeoutMs = value }
|
||||
},
|
||||
summarizer = { value -> value?.let { "${value / 1000.0} seconds" } },
|
||||
summarizer = { value ->
|
||||
value?.let {
|
||||
WholphinApplication.instance.getString(
|
||||
R.string.decimal_seconds,
|
||||
value / 1000.0,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val SeekBarSteps =
|
||||
|
|
@ -243,7 +258,7 @@ sealed interface AppPreference<T> {
|
|||
},
|
||||
summarizer = { value ->
|
||||
if (value == 0L) {
|
||||
"Disabled"
|
||||
WholphinApplication.instance.getString(R.string.disabled)
|
||||
} else {
|
||||
"${value}s"
|
||||
}
|
||||
|
|
@ -262,10 +277,16 @@ sealed interface AppPreference<T> {
|
|||
prefs.updatePlaybackPreferences { autoPlayNextDelaySeconds = value }
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value == 0L) {
|
||||
"Immediate"
|
||||
if (value == null) {
|
||||
""
|
||||
} else if (value == 0L) {
|
||||
WholphinApplication.instance.getString(R.string.immediate)
|
||||
} else {
|
||||
"$value seconds"
|
||||
WholphinApplication.instance.resources.getQuantityString(
|
||||
R.plurals.seconds,
|
||||
value.toInt(),
|
||||
value.toInt(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -284,10 +305,16 @@ sealed interface AppPreference<T> {
|
|||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value == 0L) {
|
||||
"Disabled"
|
||||
if (value == null) {
|
||||
""
|
||||
} else if (value == 0L) {
|
||||
WholphinApplication.instance.getString(R.string.disabled)
|
||||
} else {
|
||||
"$value hours"
|
||||
WholphinApplication.instance.resources.getQuantityString(
|
||||
R.plurals.hours,
|
||||
value.toInt(),
|
||||
value.toInt(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -485,7 +512,7 @@ sealed interface AppPreference<T> {
|
|||
|
||||
val SkipCommercials =
|
||||
AppChoicePreference<SkipSegmentBehavior>(
|
||||
title = R.string.skip_comercials_behavior,
|
||||
title = R.string.skip_commercials_behavior,
|
||||
defaultValue = SkipSegmentBehavior.ASK_TO_SKIP,
|
||||
getter = { it.playbackPreferences.skipCommercials },
|
||||
setter = { prefs, value ->
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.Chapter
|
||||
|
||||
@Composable
|
||||
|
|
@ -28,7 +30,7 @@ fun ChapterRow(
|
|||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = "Chapters",
|
||||
text = stringResource(R.string.chapters),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ fun PersonRow(
|
|||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = "People",
|
||||
text = stringResource(R.string.people),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
|
|
@ -19,6 +20,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -26,6 +28,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
|
|
@ -46,6 +49,7 @@ import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -65,6 +69,7 @@ class CollectionFolderViewModel
|
|||
@Inject
|
||||
constructor(
|
||||
api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||
) : ItemViewModel(api) {
|
||||
|
|
@ -83,7 +88,7 @@ class CollectionFolderViewModel
|
|||
viewModelScope.launch(
|
||||
LoadingExceptionHandler(
|
||||
loading,
|
||||
"Error loading collection $itemId",
|
||||
context.getString(R.string.error_loading_collection, itemId),
|
||||
) + Dispatchers.IO,
|
||||
) {
|
||||
if (itemId != null) {
|
||||
|
|
@ -292,7 +297,7 @@ fun CollectionFolderGridContent(
|
|||
showTitle: Boolean = true,
|
||||
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||
) {
|
||||
val title = item?.name ?: item?.data?.collectionType?.name ?: "Collection"
|
||||
val title = item?.name ?: item?.data?.collectionType?.name ?: stringResource(R.string.collection)
|
||||
val sortOptions =
|
||||
when (item?.data?.collectionType) {
|
||||
CollectionType.MOVIES -> MovieSortOptions
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -462,12 +463,13 @@ fun ConfirmDialogContent(
|
|||
}
|
||||
|
||||
fun chooseVersionParams(
|
||||
context: Context,
|
||||
sources: List<MediaSourceInfo>,
|
||||
onClick: (Int) -> Unit,
|
||||
): DialogParams =
|
||||
DialogParams(
|
||||
fromLongClick = false,
|
||||
title = "Play Version",
|
||||
title = context.getString(R.string.choose_stream, context.getString(R.string.version)),
|
||||
items =
|
||||
sources.filter { it.id.isNotNullOrBlank() }.mapIndexed { index, source ->
|
||||
val videoStream =
|
||||
|
|
@ -485,21 +487,33 @@ fun chooseVersionParams(
|
|||
},
|
||||
)
|
||||
|
||||
@StringRes
|
||||
fun resourceFor(type: MediaStreamType): Int =
|
||||
when (type) {
|
||||
MediaStreamType.AUDIO -> R.string.audio
|
||||
MediaStreamType.VIDEO -> R.string.video
|
||||
MediaStreamType.SUBTITLE -> R.string.subtitles
|
||||
MediaStreamType.EMBEDDED_IMAGE -> 0
|
||||
MediaStreamType.DATA -> 0
|
||||
MediaStreamType.LYRIC -> 0
|
||||
}
|
||||
|
||||
fun chooseStream(
|
||||
context: Context,
|
||||
streams: List<MediaStream>,
|
||||
type: MediaStreamType,
|
||||
onClick: (Int) -> Unit,
|
||||
): DialogParams =
|
||||
DialogParams(
|
||||
fromLongClick = false,
|
||||
title = "Choose ${type.serialName}", // TODO
|
||||
title = context.getString(R.string.choose_stream, context.getString(resourceFor(type))),
|
||||
items =
|
||||
buildList {
|
||||
if (type == MediaStreamType.SUBTITLE) {
|
||||
add(
|
||||
DialogItem(
|
||||
headlineContent = {
|
||||
Text(text = "None")
|
||||
Text(text = stringResource(R.string.none))
|
||||
},
|
||||
supportingContent = {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -50,7 +52,7 @@ fun ErrorMessage(
|
|||
modifier = modifier.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "An error occurred! Press the button to send logs to your server.",
|
||||
text = stringResource(R.string.compose_error_message_title),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
|
|
@ -60,7 +62,7 @@ fun ErrorMessage(
|
|||
},
|
||||
) {
|
||||
Text(
|
||||
text = "Send Logs",
|
||||
text = stringResource(R.string.send_app_logs),
|
||||
)
|
||||
}
|
||||
message?.let {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
|
|
@ -70,7 +72,7 @@ fun LoadingRow(
|
|||
->
|
||||
LoadingRowPlaceholder(
|
||||
title = title,
|
||||
message = "Loading...",
|
||||
message = stringResource(R.string.loading),
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
|
|
@ -88,7 +90,7 @@ fun LoadingRow(
|
|||
} else if (showIfEmpty) {
|
||||
LoadingRowPlaceholder(
|
||||
title = title,
|
||||
message = "No results",
|
||||
message = stringResource(R.string.no_results),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -16,7 +17,6 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
|
|||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.main.HomeRow
|
||||
import com.github.damontecres.wholphin.ui.main.HomeSection
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
|
|
@ -113,11 +113,11 @@ class RecommendedMovieViewModel
|
|||
rows.forEach { it.init() }
|
||||
val homeRows =
|
||||
listOf(
|
||||
HomeRow(HomeSection.RESUME, resumeItems, "Continue Watching"),
|
||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyReleasedItems, "Recently Released"),
|
||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
||||
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
||||
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
||||
HomeRow(R.string.continue_watching, resumeItems),
|
||||
HomeRow(R.string.recently_released, recentlyReleasedItems),
|
||||
HomeRow(R.string.recently_added, recentlyAddedItems),
|
||||
HomeRow(R.string.suggestions, suggestedItems),
|
||||
HomeRow(R.string.top_unwatched, unwatchedTopRatedItems),
|
||||
).filter { it.items.isNotEmpty() }
|
||||
withContext(Dispatchers.Main) {
|
||||
this@RecommendedMovieViewModel.rows.value = homeRows
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -16,7 +17,6 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
|
|||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.main.HomeRow
|
||||
import com.github.damontecres.wholphin.ui.main.HomeSection
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
|
|
@ -124,12 +124,12 @@ class RecommendedTvShowViewModel
|
|||
rows.forEach { it.init() }
|
||||
val homeRows =
|
||||
listOf(
|
||||
HomeRow(HomeSection.RESUME, resumeItems, "Continue Watching"),
|
||||
HomeRow(HomeSection.NEXT_UP, nextUpItems, "Next Up"),
|
||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyReleasedItems, "Recently Released"),
|
||||
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
|
||||
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
|
||||
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
|
||||
HomeRow(R.string.continue_watching, resumeItems),
|
||||
HomeRow(R.string.next_up, nextUpItems),
|
||||
HomeRow(R.string.recently_released, recentlyReleasedItems),
|
||||
HomeRow(R.string.recently_added, recentlyAddedItems),
|
||||
HomeRow(R.string.suggestions, suggestedItems),
|
||||
HomeRow(R.string.top_unwatched, unwatchedTopRatedItems),
|
||||
).filter { it.items.isNotEmpty() }
|
||||
withContext(Dispatchers.Main) {
|
||||
this@RecommendedTvShowViewModel.rows.value = homeRows
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.components.ScrollableDialog
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
|
|
@ -74,24 +76,30 @@ fun MediaSourceInfo(
|
|||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = "Name: ${source.name}",
|
||||
text = stringResource(R.string.name) + ": ${source.name}",
|
||||
)
|
||||
Text(
|
||||
text = "ID: ${source.id}",
|
||||
)
|
||||
if (showFilePath) {
|
||||
Text(
|
||||
text = "Path: ${source.path}",
|
||||
text = stringResource(R.string.path) + ": ${source.path}",
|
||||
)
|
||||
}
|
||||
source.size?.let { size ->
|
||||
Text(
|
||||
text = "Size: ${formatBytes(size)}",
|
||||
text = stringResource(R.string.file_size) + ": ${formatBytes(size)}",
|
||||
)
|
||||
}
|
||||
source.bitrate?.let { bitrate ->
|
||||
Text(
|
||||
text = "Bitrate: ${formatBytes(bitrate, byteRateSuffixes)}",
|
||||
text =
|
||||
stringResource(R.string.bitrate) + ": ${
|
||||
formatBytes(
|
||||
bitrate,
|
||||
byteRateSuffixes,
|
||||
)
|
||||
}",
|
||||
)
|
||||
}
|
||||
source.mediaStreams?.letNotEmpty { streams ->
|
||||
|
|
@ -109,7 +117,7 @@ fun MediaSourceInfo(
|
|||
stream.profile?.let(::add)
|
||||
}
|
||||
Text(
|
||||
text = "Video: " + data.joinToString(" - "),
|
||||
text = stringResource(R.string.video) + ": " + data.joinToString(" - "),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -121,10 +129,10 @@ fun MediaSourceInfo(
|
|||
stream.channelLayout?.let(::add)
|
||||
stream.bitRate?.let { add(formatBytes(it, byteRateSuffixes)) }
|
||||
if (stream.audioSpatialFormat != AudioSpatialFormat.NONE) add(stream.audioSpatialFormat.serialName)
|
||||
if (stream.isDefault) add("Default")
|
||||
if (stream.isDefault) add(stringResource(R.string.default_track))
|
||||
}
|
||||
Text(
|
||||
text = "Audio ${index + 1}: " + data.joinToString(" - "),
|
||||
text = stringResource(R.string.audio) + " ${index + 1}: " + data.joinToString(" - "),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -133,12 +141,14 @@ fun MediaSourceInfo(
|
|||
buildList {
|
||||
stream.language?.let { add(languageName(it)) }
|
||||
stream.codec?.let(::add)
|
||||
if (stream.isDefault) add("Default")
|
||||
if (stream.isForced) add("Forced")
|
||||
if (stream.isExternal) add("External")
|
||||
if (stream.isDefault) add(stringResource(R.string.default_track))
|
||||
if (stream.isForced) add(stringResource(R.string.forced_track))
|
||||
if (stream.isExternal) add(stringResource(R.string.external_track))
|
||||
}
|
||||
Text(
|
||||
text = "Subtitle ${index + 1}: " + data.joinToString(" - "),
|
||||
text =
|
||||
stringResource(R.string.subtitle) + " ${index + 1}: " +
|
||||
data.joinToString(" - "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import androidx.compose.ui.input.key.KeyEventType
|
|||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Button
|
||||
|
|
@ -298,7 +299,7 @@ fun CardGrid(
|
|||
// focusedIndex = -1
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
text = "No results",
|
||||
text = stringResource(R.string.no_results),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
|
|
@ -326,6 +327,8 @@ fun CardGrid(
|
|||
}
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
val letters = context.getString(R.string.jump_letters)
|
||||
// Letters
|
||||
val currentLetter =
|
||||
remember(focusedIndex) {
|
||||
|
|
@ -344,10 +347,11 @@ fun CardGrid(
|
|||
null
|
||||
}
|
||||
}
|
||||
?: LETTERS[0]
|
||||
?: letters[0]
|
||||
}
|
||||
if (showLetterButtons && pager.isNotEmpty()) {
|
||||
AlphabetButtons(
|
||||
letters = letters,
|
||||
currentLetter = currentLetter,
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
letterClicked = { letter ->
|
||||
|
|
@ -404,18 +408,18 @@ fun JumpButton(
|
|||
}
|
||||
}
|
||||
|
||||
private const val LETTERS = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
@Composable
|
||||
fun AlphabetButtons(
|
||||
letters: String,
|
||||
currentLetter: Char,
|
||||
letterClicked: (Char) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val index = LETTERS.indexOf(currentLetter)
|
||||
val index = letters.indexOf(currentLetter)
|
||||
LaunchedEffect(currentLetter) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
val firstVisibleItemIndex = listState.firstVisibleItemIndex
|
||||
|
|
@ -428,19 +432,19 @@ fun AlphabetButtons(
|
|||
}
|
||||
}
|
||||
}
|
||||
val focusRequesters = remember { List(LETTERS.length) { FocusRequester() } }
|
||||
val focusRequesters = remember { List(letters.length) { FocusRequester() } }
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier =
|
||||
modifier.focusProperties {
|
||||
onEnter = {
|
||||
focusRequesters[index.coerceIn(0, LETTERS.length - 1)].tryRequestFocus()
|
||||
focusRequesters[index.coerceIn(0, letters.length - 1)].tryRequestFocus()
|
||||
}
|
||||
},
|
||||
) {
|
||||
items(
|
||||
LETTERS.length,
|
||||
key = { LETTERS[it] },
|
||||
letters.length,
|
||||
key = { letters[it] },
|
||||
) { index ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
|
@ -452,17 +456,17 @@ fun AlphabetButtons(
|
|||
contentPadding = PaddingValues(2.dp),
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
letterClicked.invoke(LETTERS[index])
|
||||
letterClicked.invoke(letters[index])
|
||||
},
|
||||
) {
|
||||
val color =
|
||||
if (!focused && LETTERS[index] == currentLetter) {
|
||||
if (!focused && letters[index] == currentLetter) {
|
||||
MaterialTheme.colorScheme.tertiary
|
||||
} else {
|
||||
LocalContentColor.current
|
||||
}
|
||||
Text(
|
||||
text = LETTERS[index].toString(),
|
||||
text = letters[index].toString(),
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -33,6 +34,7 @@ import androidx.tv.material3.TabDefaults
|
|||
import androidx.tv.material3.TabRow
|
||||
import androidx.tv.material3.TabRowDefaults
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
|
|
@ -96,8 +98,8 @@ fun CollectionFolderLiveTv(
|
|||
|
||||
val tabs =
|
||||
listOf(
|
||||
TabId("Guide", UUID.randomUUID()),
|
||||
TabId("DVR Schedule", UUID.randomUUID()),
|
||||
TabId(stringResource(R.string.tv_guide), UUID.randomUUID()),
|
||||
TabId(stringResource(R.string.tv_dvr_schedule), UUID.randomUUID()),
|
||||
) + folders
|
||||
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
|
|
@ -29,6 +30,7 @@ import androidx.tv.material3.TabDefaults
|
|||
import androidx.tv.material3.TabRow
|
||||
import androidx.tv.material3.TabRowDefaults
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -52,7 +54,13 @@ fun CollectionFolderMovie(
|
|||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs = listOf("Recommended", "Library", "Collections", "Genres")
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.recommended),
|
||||
stringResource(R.string.library),
|
||||
stringResource(R.string.collections),
|
||||
stringResource(R.string.genres),
|
||||
)
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
|
|
@ -29,6 +30,7 @@ import androidx.tv.material3.TabDefaults
|
|||
import androidx.tv.material3.TabRow
|
||||
import androidx.tv.material3.TabRowDefaults
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -52,7 +54,12 @@ fun CollectionFolderTv(
|
|||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs = listOf("Recommended", "Library", "Genres")
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.recommended),
|
||||
stringResource(R.string.library),
|
||||
stringResource(R.string.genres),
|
||||
)
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
|
|
@ -36,6 +37,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
* @param onChooseTracks callback to pick a track for the given type of the item
|
||||
*/
|
||||
fun buildMoreDialogItems(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
series: BaseItem?,
|
||||
sourceId: UUID?,
|
||||
|
|
@ -50,7 +52,7 @@ fun buildMoreDialogItems(
|
|||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
"Play",
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
@ -83,7 +85,7 @@ fun buildMoreDialogItems(
|
|||
series?.let {
|
||||
add(
|
||||
DialogItem(
|
||||
"Go to series",
|
||||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
) {
|
||||
navigateTo(
|
||||
|
|
@ -100,7 +102,10 @@ fun buildMoreDialogItems(
|
|||
if (sources.size > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
"Choose Version",
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.version),
|
||||
),
|
||||
R.string.fa_file_video,
|
||||
) {
|
||||
onChooseVersion.invoke()
|
||||
|
|
@ -116,7 +121,10 @@ fun buildMoreDialogItems(
|
|||
if (audioCount > 1) {
|
||||
add(
|
||||
DialogItem(
|
||||
"Choose audio",
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.audio),
|
||||
),
|
||||
R.string.fa_volume_low,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.AUDIO)
|
||||
|
|
@ -126,7 +134,10 @@ fun buildMoreDialogItems(
|
|||
if (subtitleCount > 0) {
|
||||
add(
|
||||
DialogItem(
|
||||
"Choose subtitles",
|
||||
context.getString(
|
||||
R.string.choose_stream,
|
||||
context.getString(R.string.subtitles),
|
||||
),
|
||||
R.string.fa_closed_captioning,
|
||||
) {
|
||||
onChooseTracks.invoke(MediaStreamType.SUBTITLE)
|
||||
|
|
@ -138,6 +149,7 @@ fun buildMoreDialogItems(
|
|||
}
|
||||
|
||||
fun buildMoreDialogItemsForHome(
|
||||
context: Context,
|
||||
item: BaseItem,
|
||||
seriesId: UUID?,
|
||||
playbackPosition: Duration,
|
||||
|
|
@ -151,7 +163,7 @@ fun buildMoreDialogItemsForHome(
|
|||
val itemId = item.id
|
||||
add(
|
||||
DialogItem(
|
||||
"Go To",
|
||||
context.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
navigateTo(item.destination())
|
||||
|
|
@ -161,7 +173,7 @@ fun buildMoreDialogItemsForHome(
|
|||
if (playbackPosition >= 1.seconds) {
|
||||
add(
|
||||
DialogItem(
|
||||
"Resume",
|
||||
context.getString(R.string.resume),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
@ -175,7 +187,7 @@ fun buildMoreDialogItemsForHome(
|
|||
)
|
||||
add(
|
||||
DialogItem(
|
||||
"Restart",
|
||||
context.getString(R.string.restart),
|
||||
Icons.Default.Refresh,
|
||||
// iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
@ -190,7 +202,7 @@ fun buildMoreDialogItemsForHome(
|
|||
} else {
|
||||
add(
|
||||
DialogItem(
|
||||
"Play",
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
@ -224,7 +236,7 @@ fun buildMoreDialogItemsForHome(
|
|||
seriesId?.let {
|
||||
add(
|
||||
DialogItem(
|
||||
"Go to series",
|
||||
context.getString(R.string.go_to_series),
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
) {
|
||||
navigateTo(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
|
|
@ -29,6 +30,7 @@ import androidx.tv.material3.TabDefaults
|
|||
import androidx.tv.material3.TabRow
|
||||
import androidx.tv.material3.TabRowDefaults
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -55,7 +57,12 @@ fun FavoritesPage(
|
|||
)
|
||||
}
|
||||
|
||||
val tabs = listOf("Movies", "TV Shows", "Episodes")
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.movies),
|
||||
stringResource(R.string.tv_shows),
|
||||
stringResource(R.string.episodes),
|
||||
)
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -33,6 +34,7 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
|
|
@ -258,7 +260,7 @@ fun PersonPageContent(
|
|||
}
|
||||
item {
|
||||
LoadingRow(
|
||||
title = rowTitle("Movies", movies),
|
||||
title = rowTitle(stringResource(R.string.movies), movies),
|
||||
state = movies,
|
||||
rowIndex = MOVIE_ROW,
|
||||
position = position,
|
||||
|
|
@ -271,7 +273,7 @@ fun PersonPageContent(
|
|||
}
|
||||
item {
|
||||
LoadingRow(
|
||||
title = rowTitle("Series", series),
|
||||
title = rowTitle(stringResource(R.string.tv_shows), series),
|
||||
state = series,
|
||||
rowIndex = SERIES_ROW,
|
||||
position = position,
|
||||
|
|
@ -284,7 +286,7 @@ fun PersonPageContent(
|
|||
}
|
||||
item {
|
||||
LoadingRow(
|
||||
title = rowTitle("Episodes", episodes),
|
||||
title = rowTitle(stringResource(R.string.episodes), episodes),
|
||||
state = episodes,
|
||||
rowIndex = EPISODE_ROW,
|
||||
position = position,
|
||||
|
|
@ -365,9 +367,14 @@ fun PersonHeader(
|
|||
val age = if (deathdate == null) birthdate.until(LocalDate.now())?.years else null
|
||||
val text =
|
||||
if (age != null) {
|
||||
"Born: ${formatDate(it)} ($age years old)"
|
||||
stringResource(R.string.born) + ": ${formatDate(it)} (${
|
||||
stringResource(
|
||||
R.string.years_old,
|
||||
age,
|
||||
)
|
||||
})"
|
||||
} else {
|
||||
"Born: ${formatDate(it)}"
|
||||
stringResource(R.string.born) + ": ${formatDate(it)}"
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
|
|
@ -379,7 +386,7 @@ fun PersonHeader(
|
|||
}
|
||||
birthPlace?.let {
|
||||
Text(
|
||||
text = "Birthplace: $it",
|
||||
text = stringResource(R.string.birthplace) + ": $it",
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
|
|
@ -390,9 +397,14 @@ fun PersonHeader(
|
|||
val age = birthdate?.until(it)?.years
|
||||
val text =
|
||||
if (age != null) {
|
||||
"Died: ${formatDate(it)} ($age years old)"
|
||||
stringResource(R.string.died) + ": ${formatDate(it)} (${
|
||||
stringResource(
|
||||
R.string.years_old,
|
||||
age,
|
||||
)
|
||||
})"
|
||||
} else {
|
||||
"Died: ${formatDate(it)}"
|
||||
stringResource(R.string.died) + ": ${formatDate(it)}"
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -123,6 +125,7 @@ fun PlaylistDetails(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: PlaylistViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init(destination.itemId)
|
||||
}
|
||||
|
|
@ -170,13 +173,13 @@ fun PlaylistDetails(
|
|||
items =
|
||||
listOf(
|
||||
DialogItem(
|
||||
"Go to",
|
||||
context.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
DialogItem(
|
||||
"Play from here",
|
||||
context.getString(R.string.play_from_here),
|
||||
Icons.Default.PlayArrow,
|
||||
) {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
|
|
@ -282,7 +285,7 @@ fun PlaylistDetailsContent(
|
|||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = playlist.name ?: "Playlist",
|
||||
text = playlist.name ?: stringResource(R.string.playlist),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.displayMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -31,6 +32,7 @@ import androidx.tv.material3.ListItemDefaults
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -202,7 +204,7 @@ fun DvrScheduleContent(
|
|||
if (activeRecordings.isEmpty() && scheduledRecordings.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "No scheduled recordings",
|
||||
text = stringResource(R.string.no_scheduled_recordings),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
@ -213,7 +215,7 @@ fun DvrScheduleContent(
|
|||
if (activeRecordings.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Active",
|
||||
text = stringResource(R.string.active_recordings),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.livetv
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode
|
||||
|
|
@ -16,6 +18,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -45,6 +48,7 @@ const val MAX_HOURS = 48L
|
|||
class LiveTvViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
val api: ApiClient,
|
||||
val navigationManager: NavigationManager,
|
||||
) : ViewModel() {
|
||||
|
|
@ -169,7 +173,7 @@ class LiveTvViewModel
|
|||
startHours = it.toFloat(),
|
||||
endHours = (it + 1).toFloat(),
|
||||
duration = 60.seconds,
|
||||
name = "No data",
|
||||
name = context.getString(R.string.no_data),
|
||||
subtitle = null,
|
||||
seasonEpisode = null,
|
||||
isRecording = false,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
|
@ -30,6 +31,7 @@ import androidx.tv.material3.Icon
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -165,7 +167,14 @@ fun ProgramDialog(
|
|||
)
|
||||
}
|
||||
Text(
|
||||
text = if (isSeriesRecording) "Cancel Series Recording" else "Record Series",
|
||||
text =
|
||||
if (isSeriesRecording) {
|
||||
stringResource(
|
||||
R.string.cancel_series_recording,
|
||||
)
|
||||
} else {
|
||||
stringResource(R.string.record_series)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -193,7 +202,16 @@ fun ProgramDialog(
|
|||
)
|
||||
}
|
||||
Text(
|
||||
text = if (isRecording) "Cancel Recording" else "Record Program",
|
||||
text =
|
||||
if (isRecording) {
|
||||
stringResource(
|
||||
R.string.cancel_recording,
|
||||
)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.record_program,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -210,11 +228,11 @@ fun ProgramDialog(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = "Delete",
|
||||
contentDescription = stringResource(R.string.delete),
|
||||
tint = Color.Green.copy(alpha = .75f),
|
||||
)
|
||||
Text(
|
||||
text = "Watch live",
|
||||
text = stringResource(R.string.watch_live),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ fun MovieDetails(
|
|||
overviewOnClick = {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title = movie.name ?: "Unknown",
|
||||
title = movie.name ?: context.getString(R.string.unknown),
|
||||
overview = movie.data.overview,
|
||||
files = movie.data.mediaSources.orEmpty(),
|
||||
)
|
||||
|
|
@ -162,6 +162,7 @@ fun MovieDetails(
|
|||
title = movie.name + " (${movie.data.productionYear ?: ""})",
|
||||
items =
|
||||
buildMoreDialogItems(
|
||||
context = context,
|
||||
item = movie,
|
||||
watched = movie.data.userData?.played ?: false,
|
||||
favorite = movie.data.userData?.isFavorite ?: false,
|
||||
|
|
@ -172,7 +173,10 @@ fun MovieDetails(
|
|||
onClickFavorite = viewModel::setFavorite,
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(movie.data.mediaSources!!) { idx ->
|
||||
chooseVersionParams(
|
||||
context,
|
||||
movie.data.mediaSources!!,
|
||||
) { idx ->
|
||||
val source = movie.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
movie,
|
||||
|
|
@ -188,6 +192,7 @@ fun MovieDetails(
|
|||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
type = type,
|
||||
onClick = { trackIndex ->
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ fun MovieDetailsHeader(
|
|||
?.joinToString(", ") { it.name!! }
|
||||
?.let {
|
||||
Text(
|
||||
text = "Directed by $it",
|
||||
text = stringResource(R.string.directed_by, it),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -183,7 +183,7 @@ fun MovieDetailsHeader(
|
|||
?.let {
|
||||
if (it.isNotNullOrBlank()) {
|
||||
TitleValueText(
|
||||
"Subtitles",
|
||||
stringResource(R.string.subtitles),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.movie
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
|
|
@ -25,6 +27,7 @@ import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -43,6 +46,7 @@ class MovieViewModel
|
|||
@Inject
|
||||
constructor(
|
||||
api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val navigationManager: NavigationManager,
|
||||
val serverRepository: ServerRepository,
|
||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
|
|
@ -79,7 +83,7 @@ class MovieViewModel
|
|||
// TODO would be nice to clean up the trailer name
|
||||
// ?.replace(item.name ?: "", "")
|
||||
// ?.removePrefix(" - ")
|
||||
?: "Trailer"
|
||||
?: context.getString(R.string.trailer)
|
||||
RemoteTrailer(name, url)
|
||||
}
|
||||
}.orEmpty()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ fun FocusedEpisodeFooter(
|
|||
?.let {
|
||||
if (it.isNotNullOrBlank()) {
|
||||
TitleValueText(
|
||||
"Subtitles",
|
||||
stringResource(R.string.subtitles),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 160.dp),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.series
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -33,6 +34,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -83,6 +85,7 @@ fun SeriesDetails(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: SeriesViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init(preferences, destination.itemId, destination.item, null)
|
||||
}
|
||||
|
|
@ -136,7 +139,8 @@ fun SeriesDetails(
|
|||
onLongClickItem = { season ->
|
||||
seasonDialog =
|
||||
buildDialogForSeason(
|
||||
season,
|
||||
context = context,
|
||||
s = season,
|
||||
onClickItem = { viewModel.navigateTo(it.destination()) },
|
||||
markPlayed = { played ->
|
||||
viewModel.setSeasonWatched(season.id, played)
|
||||
|
|
@ -146,7 +150,7 @@ fun SeriesDetails(
|
|||
overviewOnClick = {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title = item.name ?: "Unknown",
|
||||
title = item.name ?: context.getString(R.string.unknown),
|
||||
overview = item.data.overview,
|
||||
files = listOf(),
|
||||
)
|
||||
|
|
@ -161,7 +165,8 @@ fun SeriesDetails(
|
|||
if (showWatchConfirmation) {
|
||||
ConfirmDialog(
|
||||
title = item.name ?: "",
|
||||
body = if (played) "Mark entire series as unplayed?" else "Mark entire series as played?",
|
||||
body =
|
||||
stringResource(if (played) R.string.mark_entire_series_as_unplayed else R.string.mark_entire_series_as_played),
|
||||
onCancel = {
|
||||
showWatchConfirmation = false
|
||||
},
|
||||
|
|
@ -343,7 +348,7 @@ fun SeriesDetailsContent(
|
|||
}
|
||||
item {
|
||||
ItemRow(
|
||||
title = "Seasons",
|
||||
title = stringResource(R.string.tv_seasons),
|
||||
items = seasons,
|
||||
onClickItem = {
|
||||
position = SEASONS_ROW
|
||||
|
|
@ -444,7 +449,7 @@ fun SeriesDetailsHeader(
|
|||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = series.name ?: "Unknown",
|
||||
text = series.name ?: stringResource(R.string.unknown),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -487,6 +492,7 @@ fun SeriesDetailsHeader(
|
|||
}
|
||||
|
||||
fun buildDialogForSeason(
|
||||
context: Context,
|
||||
s: BaseItem,
|
||||
onClickItem: (BaseItem) -> Unit,
|
||||
markPlayed: (Boolean) -> Unit,
|
||||
|
|
@ -494,26 +500,26 @@ fun buildDialogForSeason(
|
|||
val items =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem("Go to", Icons.Default.PlayArrow) {
|
||||
DialogItem(context.getString(R.string.go_to), Icons.Default.PlayArrow) {
|
||||
onClickItem.invoke(s)
|
||||
},
|
||||
)
|
||||
if (s.data.userData?.played == true) {
|
||||
add(
|
||||
DialogItem("Mark as unplayed", R.string.fa_eye) {
|
||||
DialogItem(context.getString(R.string.mark_unwatched), R.string.fa_eye) {
|
||||
markPlayed.invoke(false)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
DialogItem("Mark as played", R.string.fa_eye_slash) {
|
||||
DialogItem(context.getString(R.string.mark_watched), R.string.fa_eye_slash) {
|
||||
markPlayed.invoke(true)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return DialogParams(
|
||||
title = s.name ?: "Season",
|
||||
title = s.name ?: context.getString(R.string.tv_season),
|
||||
fromLongClick = true,
|
||||
items = items,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -72,6 +74,7 @@ fun SeriesOverview(
|
|||
viewModel: SeriesViewModel = hiltViewModel(),
|
||||
initialSeasonEpisode: SeasonEpisodeIds? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val firstItemFocusRequester = remember { FocusRequester() }
|
||||
val episodeRowFocusRequester = remember { FocusRequester() }
|
||||
|
||||
|
|
@ -178,7 +181,8 @@ fun SeriesOverview(
|
|||
title = series.name + " - " + ep.data.seasonEpisode,
|
||||
items =
|
||||
buildMoreDialogItems(
|
||||
ep,
|
||||
context = context,
|
||||
item = ep,
|
||||
watched = ep.data.userData?.played ?: false,
|
||||
favorite = ep.data.userData?.isFavorite ?: false,
|
||||
series = series,
|
||||
|
|
@ -208,7 +212,10 @@ fun SeriesOverview(
|
|||
},
|
||||
onChooseVersion = {
|
||||
chooseVersion =
|
||||
chooseVersionParams(ep.data.mediaSources!!) { idx ->
|
||||
chooseVersionParams(
|
||||
context,
|
||||
ep.data.mediaSources!!,
|
||||
) { idx ->
|
||||
val source = ep.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
ep,
|
||||
|
|
@ -224,6 +231,7 @@ fun SeriesOverview(
|
|||
)?.let { source ->
|
||||
chooseVersion =
|
||||
chooseStream(
|
||||
context = context,
|
||||
streams = source.mediaStreams.orEmpty(),
|
||||
type = type,
|
||||
onClick = { trackIndex ->
|
||||
|
|
@ -313,7 +321,7 @@ fun SeriesOverview(
|
|||
episodeList?.getOrNull(position.episodeRowIndex)?.let {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title = it.name ?: "Unknown",
|
||||
title = it.name ?: context.getString(R.string.unknown),
|
||||
overview = it.data.overview,
|
||||
files = it.data.mediaSources.orEmpty(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
|
|
@ -42,6 +43,7 @@ import androidx.tv.material3.TabRow
|
|||
import androidx.tv.material3.TabRowDefaults
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.aspectRatioFloat
|
||||
|
|
@ -178,7 +180,9 @@ fun SeriesOverviewContent(
|
|||
.focusRequester(focusRequesters[index]),
|
||||
) {
|
||||
Text(
|
||||
text = season.name ?: "Season ${season.data.indexNumber}",
|
||||
text =
|
||||
season.name
|
||||
?: (stringResource(R.string.tv_season) + " ${season.data.indexNumber}"),
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.wholphin.ui.main
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -71,10 +72,20 @@ import org.jellyfin.sdk.model.extensions.ticks
|
|||
import kotlin.time.Duration
|
||||
|
||||
data class HomeRow(
|
||||
val section: HomeSection,
|
||||
@param:StringRes val titleRes: Int?,
|
||||
val title: String?,
|
||||
val items: List<BaseItem?>,
|
||||
val title: String? = null,
|
||||
)
|
||||
) {
|
||||
constructor(
|
||||
@StringRes titleRes: Int,
|
||||
items: List<BaseItem?>,
|
||||
) : this(titleRes, null, items)
|
||||
|
||||
constructor(
|
||||
title: String,
|
||||
items: List<BaseItem?>,
|
||||
) : this(null, title, items)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomePage(
|
||||
|
|
@ -121,6 +132,7 @@ fun HomePage(
|
|||
onLongClickItem = {
|
||||
val dialogItems =
|
||||
buildMoreDialogItemsForHome(
|
||||
context = context,
|
||||
item = it,
|
||||
seriesId = it.data.seriesId,
|
||||
playbackPosition =
|
||||
|
|
@ -242,7 +254,7 @@ fun HomePageContent(
|
|||
itemsIndexed(homeRows) { rowIndex, row ->
|
||||
if (row.items.isNotEmpty()) {
|
||||
ItemRow(
|
||||
title = row.title ?: stringResource(row.section.nameRes),
|
||||
title = row.title ?: row.titleRes?.let { stringResource(it) } ?: "",
|
||||
items = row.items,
|
||||
onClickItem = onClickItem,
|
||||
cardOnFocus = { isFocused, index ->
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package com.github.damontecres.wholphin.ui.main
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.github.damontecres.wholphin.R
|
||||
|
||||
/**
|
||||
* All possible homesections, "synced" with jellyfin-web.
|
||||
*
|
||||
* https://github.com/jellyfin/jellyfin-web/blob/master/src/components/homesections/homesections.js
|
||||
*/
|
||||
enum class HomeSection(
|
||||
val key: String,
|
||||
@param:StringRes val nameRes: Int,
|
||||
) {
|
||||
LATEST_MEDIA("latestmedia", R.string.home_section_latest_media),
|
||||
LIBRARY_TILES_SMALL("smalllibrarytiles", R.string.home_section_library),
|
||||
LIBRARY_BUTTONS("librarybuttons", R.string.home_section_library_small),
|
||||
RESUME("resume", R.string.home_section_resume),
|
||||
RESUME_AUDIO("resumeaudio", R.string.home_section_resume_audio),
|
||||
RESUME_BOOK("resumebook", R.string.home_section_resume_book),
|
||||
ACTIVE_RECORDINGS("activerecordings", R.string.home_section_active_recordings),
|
||||
NEXT_UP("nextup", R.string.home_section_next_up),
|
||||
LIVE_TV("livetv", R.string.home_section_livetv),
|
||||
NONE("none", R.string.home_section_none),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromKey(key: String): HomeSection = entries.firstOrNull { it.key == key } ?: NONE
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.github.damontecres.wholphin.ui.main
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
|
|
@ -15,6 +17,7 @@ import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.supportItemKinds
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -40,6 +43,7 @@ import javax.inject.Inject
|
|||
class HomeViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
val api: ApiClient,
|
||||
val navigationManager: NavigationManager,
|
||||
val serverRepository: ServerRepository,
|
||||
|
|
@ -86,7 +90,7 @@ class HomeViewModel
|
|||
if (resume.isNotEmpty()) {
|
||||
add(
|
||||
HomeRow(
|
||||
section = HomeSection.RESUME,
|
||||
titleRes = R.string.recently_added,
|
||||
items = resume,
|
||||
),
|
||||
)
|
||||
|
|
@ -94,7 +98,7 @@ class HomeViewModel
|
|||
if (nextUp.isNotEmpty()) {
|
||||
add(
|
||||
HomeRow(
|
||||
section = HomeSection.NEXT_UP,
|
||||
titleRes = R.string.next_up,
|
||||
items = nextUp,
|
||||
),
|
||||
)
|
||||
|
|
@ -191,10 +195,10 @@ class HomeViewModel
|
|||
.mapNotNull { view ->
|
||||
val title =
|
||||
if (view.collectionType == CollectionType.LIVETV) {
|
||||
"Recently Recorded"
|
||||
context.getString(R.string.recently_recorded)
|
||||
} else {
|
||||
view.name?.let { "Recently Added in $it" }
|
||||
}
|
||||
view.name?.let { context.getString(R.string.recently_added_in, it) }
|
||||
} ?: context.getString(R.string.recently_added)
|
||||
val viewId =
|
||||
if (view.collectionType == CollectionType.LIVETV) {
|
||||
api.liveTvApi
|
||||
|
|
@ -222,9 +226,8 @@ class HomeViewModel
|
|||
.content
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
HomeRow(
|
||||
section = HomeSection.LATEST_MEDIA,
|
||||
items = latest,
|
||||
title = title,
|
||||
items = latest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -28,6 +30,7 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
|
|
@ -153,6 +156,7 @@ fun SearchPage(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: SearchViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val movies by viewModel.movies.observeAsState(SearchResult.NoQuery)
|
||||
val collections by viewModel.collections.observeAsState(SearchResult.NoQuery)
|
||||
val series by viewModel.series.observeAsState(SearchResult.NoQuery)
|
||||
|
|
@ -198,7 +202,7 @@ fun SearchPage(
|
|||
}
|
||||
}
|
||||
searchResultRow(
|
||||
title = "Movies",
|
||||
title = context.getString(R.string.movies),
|
||||
result = movies,
|
||||
rowIndex = MOVIE_ROW,
|
||||
position = position,
|
||||
|
|
@ -208,7 +212,7 @@ fun SearchPage(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
searchResultRow(
|
||||
title = "Collections",
|
||||
title = context.getString(R.string.collections),
|
||||
result = collections,
|
||||
rowIndex = COLLECTION_ROW,
|
||||
position = position,
|
||||
|
|
@ -218,7 +222,7 @@ fun SearchPage(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
searchResultRow(
|
||||
title = "Series",
|
||||
title = context.getString(R.string.tv_shows),
|
||||
result = series,
|
||||
rowIndex = SERIES_ROW,
|
||||
position = position,
|
||||
|
|
@ -228,7 +232,7 @@ fun SearchPage(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
searchResultRow(
|
||||
title = "Episodes",
|
||||
title = context.getString(R.string.episodes),
|
||||
result = episodes,
|
||||
rowIndex = EPISODE_ROW,
|
||||
position = position,
|
||||
|
|
@ -308,7 +312,7 @@ fun LazyListScope.searchResultRow(
|
|||
SearchResult.Searching ->
|
||||
SearchResultPlaceholder(
|
||||
title = title,
|
||||
message = "Searching...",
|
||||
message = stringResource(R.string.searching),
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
|
|
@ -316,7 +320,7 @@ fun LazyListScope.searchResultRow(
|
|||
if (r.items.isEmpty()) {
|
||||
SearchResultPlaceholder(
|
||||
title = title,
|
||||
message = "No results",
|
||||
message = stringResource(R.string.no_results),
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ fun NavDrawer(
|
|||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = -2 }
|
||||
IconNavItem(
|
||||
text = "Search",
|
||||
text = stringResource(R.string.search),
|
||||
icon = Icons.Default.Search,
|
||||
selected = selectedIndex == -2,
|
||||
interactionSource = interactionSource,
|
||||
|
|
@ -345,7 +345,7 @@ fun NavDrawer(
|
|||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = -1 }
|
||||
IconNavItem(
|
||||
text = "Home",
|
||||
text = stringResource(R.string.home),
|
||||
icon = Icons.Default.Home,
|
||||
selected = selectedIndex == -1,
|
||||
interactionSource = interactionSource,
|
||||
|
|
@ -413,7 +413,7 @@ fun NavDrawer(
|
|||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
||||
IconNavItem(
|
||||
text = "Settings",
|
||||
text = stringResource(R.string.settings),
|
||||
icon = Icons.Default.Settings,
|
||||
selected = false,
|
||||
interactionSource = interactionSource,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import androidx.compose.ui.draw.drawBehind
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Icon
|
||||
|
|
@ -35,6 +37,7 @@ import androidx.tv.material3.ListItem
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItemDivider
|
||||
|
|
@ -58,7 +61,7 @@ fun DownloadSubtitlesContent(
|
|||
SubtitleSearch.Searching -> {
|
||||
Wrapper {
|
||||
Text(
|
||||
text = "Searching...",
|
||||
text = stringResource(R.string.searching),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -68,7 +71,7 @@ fun DownloadSubtitlesContent(
|
|||
SubtitleSearch.Downloading -> {
|
||||
Wrapper {
|
||||
Text(
|
||||
text = "Downloading...",
|
||||
text = stringResource(R.string.downloading),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -199,9 +202,15 @@ fun convertRemoteSubtitles(
|
|||
op.providerName?.let(::add)
|
||||
op.threeLetterIsoLanguageName?.let(::add)
|
||||
if (op.forced == true) {
|
||||
add("Forced")
|
||||
add(stringResource(R.string.forced_track))
|
||||
}
|
||||
add("${abbreviateNumber(op.downloadCount ?: 0)} downloads")
|
||||
add(
|
||||
pluralStringResource(
|
||||
R.plurals.downloads,
|
||||
op.downloadCount ?: 0,
|
||||
abbreviateNumber(op.downloadCount ?: 0),
|
||||
),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = strings.joinToString(" - "),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.github.damontecres.wholphin.ui.playback
|
||||
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.WholphinApplication
|
||||
|
||||
data class SubtitleStream(
|
||||
val index: Int,
|
||||
val language: String?,
|
||||
|
|
@ -17,7 +20,8 @@ data class SubtitleStream(
|
|||
language,
|
||||
title,
|
||||
codec,
|
||||
).joinToString(" - ").ifBlank { "Unknown" }
|
||||
).joinToString(" - ")
|
||||
.ifBlank { WholphinApplication.instance.getString(R.string.unknown) }
|
||||
}
|
||||
|
||||
data class AudioStream(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -34,6 +35,7 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
|
|
@ -68,7 +70,7 @@ fun NextUpEpisode(
|
|||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Up Next...",
|
||||
text = stringResource(R.string.next_up) + "...",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
|
@ -66,6 +67,7 @@ import com.github.damontecres.wholphin.ui.seekBack
|
|||
import com.github.damontecres.wholphin.ui.seekForward
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.stringRes
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -212,7 +214,7 @@ fun PlaybackControls(
|
|||
.padding(end = 32.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Skip ${segment.type.serialName}",
|
||||
text = stringResource(R.string.skip) + " " + stringResource(segment.type.stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -332,7 +334,7 @@ fun LeftPlaybackButtons(
|
|||
val options =
|
||||
buildList {
|
||||
addAll(moreButtonOptions.options.keys)
|
||||
add(if (showDebugInfo) "Hide debug info" else "Show debug info")
|
||||
add(stringResource(if (showDebugInfo) R.string.hide_debug_info else R.string.show_debug_info))
|
||||
}
|
||||
BottomDialog(
|
||||
choices = options,
|
||||
|
|
@ -407,7 +409,12 @@ fun RightPlaybackButtons(
|
|||
val currentChoice =
|
||||
subtitleStreams.indexOfFirstOrNull { it.index == subtitleIndex } ?: subtitleStreams.size
|
||||
BottomDialog(
|
||||
choices = options + listOf("None", "Search & Download"),
|
||||
choices =
|
||||
options +
|
||||
listOf(
|
||||
stringResource(R.string.none),
|
||||
stringResource(R.string.search_and_download),
|
||||
),
|
||||
currentChoice = currentChoice,
|
||||
onDismissRequest = {
|
||||
onControllerInteraction.invoke()
|
||||
|
|
@ -434,7 +441,12 @@ fun RightPlaybackButtons(
|
|||
)
|
||||
}
|
||||
if (showOptionsDialog) {
|
||||
val options = listOf("Audio Track", "Playback Speed", "Video Scale")
|
||||
val options =
|
||||
listOf(
|
||||
stringResource(R.string.audio),
|
||||
stringResource(R.string.playback_speed),
|
||||
stringResource(R.string.video_scale),
|
||||
)
|
||||
BottomDialog(
|
||||
choices = options,
|
||||
currentChoice = null,
|
||||
|
|
|
|||
|
|
@ -38,11 +38,13 @@ import androidx.compose.ui.input.key.type
|
|||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.media3.common.Player
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Chapter
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
|
|
@ -199,7 +201,7 @@ fun PlaybackOverlay(
|
|||
},
|
||||
) {
|
||||
Text(
|
||||
text = "Chapters",
|
||||
text = stringResource(R.string.chapters),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
LazyRow(
|
||||
|
|
@ -240,7 +242,7 @@ fun PlaybackOverlay(
|
|||
}
|
||||
if (playlist.hasNext()) {
|
||||
Text(
|
||||
text = "Queue",
|
||||
text = stringResource(R.string.queue),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -282,7 +284,7 @@ fun PlaybackOverlay(
|
|||
},
|
||||
) {
|
||||
Text(
|
||||
text = "Queue",
|
||||
text = stringResource(R.string.queue),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
LazyRow(
|
||||
|
|
@ -489,7 +491,7 @@ fun Controller(
|
|||
when (nextState) {
|
||||
OverlayViewState.CHAPTERS ->
|
||||
Text(
|
||||
text = "Chapters",
|
||||
text = stringResource(R.string.chapters),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -501,7 +503,7 @@ fun Controller(
|
|||
|
||||
OverlayViewState.QUEUE ->
|
||||
Text(
|
||||
text = "Queue",
|
||||
text = stringResource(R.string.queue),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
|
|
@ -60,6 +61,7 @@ import androidx.tv.material3.Button
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Playlist
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
|
|
@ -72,6 +74,7 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
|
|||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import com.github.damontecres.wholphin.util.stringRes
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
|
|
@ -411,7 +414,7 @@ fun PlaybackPage(
|
|||
modifier = Modifier.focusRequester(focusRequester),
|
||||
) {
|
||||
Text(
|
||||
text = "Skip ${segment.type.serialName}",
|
||||
text = stringResource(R.string.skip) + " " + stringResource(segment.type.stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.media3.common.Tracks
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
|
|
@ -963,7 +964,7 @@ class PlaybackViewModel
|
|||
if (maxAttempts == 0) {
|
||||
showToast(
|
||||
context,
|
||||
"Download is taking a long time, you may need to restart playback",
|
||||
context.getString(R.string.subtitle_download_too_long),
|
||||
)
|
||||
} else {
|
||||
// Find the new subtitle stream
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ fun PreferencesContent(
|
|||
}
|
||||
val screenTitle =
|
||||
when (preferenceScreenOption) {
|
||||
PreferenceScreenOption.BASIC -> "Preferences"
|
||||
PreferenceScreenOption.ADVANCED -> "Advanced Preferences"
|
||||
PreferenceScreenOption.USER_INTERFACE -> "User Interface Preferences"
|
||||
PreferenceScreenOption.BASIC -> R.string.settings
|
||||
PreferenceScreenOption.ADVANCED -> R.string.advanced_settings
|
||||
PreferenceScreenOption.USER_INTERFACE -> R.string.ui_interface
|
||||
}
|
||||
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
|
|
@ -126,7 +126,7 @@ fun PreferencesContent(
|
|||
) {
|
||||
stickyHeader {
|
||||
Text(
|
||||
text = screenTitle,
|
||||
text = stringResource(screenTitle),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.ui.input.key.KeyEventType
|
|||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -42,6 +43,7 @@ import androidx.tv.material3.Button
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -221,7 +223,7 @@ fun InstallUpdatePageContent(
|
|||
).padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Update available",
|
||||
text = stringResource(R.string.update_available),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -234,14 +236,14 @@ fun InstallUpdatePageContent(
|
|||
onClick = onInstallRelease,
|
||||
) {
|
||||
Text(
|
||||
text = "Download & Update",
|
||||
text = stringResource(R.string.download_and_update),
|
||||
)
|
||||
}
|
||||
Button(
|
||||
onClick = onCancel,
|
||||
) {
|
||||
Text(
|
||||
text = "Cancel",
|
||||
text = stringResource(R.string.cancel),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.tv.material3.Icon
|
||||
|
|
@ -96,7 +97,7 @@ fun ServerList(
|
|||
ListItem(
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = { Text(text = "Add Server") },
|
||||
headlineContent = { Text(text = stringResource(R.string.add_server)) },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
|
|
@ -116,11 +117,14 @@ fun ServerList(
|
|||
title = server.name ?: server.url,
|
||||
dialogItems =
|
||||
listOf(
|
||||
DialogItem("Switch", R.string.fa_arrow_left_arrow_right) {
|
||||
DialogItem(
|
||||
stringResource(R.string.switch_servers),
|
||||
R.string.fa_arrow_left_arrow_right,
|
||||
) {
|
||||
onSwitchServer.invoke(server)
|
||||
},
|
||||
DialogItem(
|
||||
"Delete",
|
||||
stringResource(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
Color.Red.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -31,6 +32,7 @@ import androidx.tv.material3.Button
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
|
|
@ -78,7 +80,7 @@ fun SwitchServerContent(
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = "Select Server",
|
||||
text = stringResource(R.string.select_server),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -120,13 +122,13 @@ fun SwitchServerContent(
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = "Discovered Servers",
|
||||
text = stringResource(R.string.discovered_servers),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
if (discoveredServers.isEmpty()) {
|
||||
Text(
|
||||
text = "Searching...",
|
||||
text = stringResource(R.string.searching),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -184,7 +186,7 @@ fun SwitchServerContent(
|
|||
.fillMaxWidth(.4f),
|
||||
) {
|
||||
Text(
|
||||
text = "Enter Server URL",
|
||||
text = stringResource(R.string.enter_server_url),
|
||||
)
|
||||
EditTextBox(
|
||||
value = url,
|
||||
|
|
@ -227,7 +229,7 @@ fun SwitchServerContent(
|
|||
if (state == LoadingState.Loading) {
|
||||
CircularProgress(Modifier.size(32.dp))
|
||||
} else {
|
||||
Text(text = "Submit")
|
||||
Text(text = stringResource(R.string.submit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
|
|
@ -37,6 +38,7 @@ import androidx.tv.material3.Button
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
|
|
@ -94,7 +96,7 @@ fun SwitchUserContent(
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = "Select User",
|
||||
text = stringResource(R.string.select_user),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ fun UserList(
|
|||
ListItem(
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = { Text(text = "Add User") },
|
||||
headlineContent = { Text(text = stringResource(R.string.add_user)) },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
|
|
@ -84,7 +84,7 @@ fun UserList(
|
|||
ListItem(
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = { Text(text = "Switch servers") },
|
||||
headlineContent = { Text(text = stringResource(R.string.switch_servers)) },
|
||||
leadingContent = {
|
||||
Text(
|
||||
text = stringResource(R.string.fa_arrow_left_arrow_right),
|
||||
|
|
@ -102,11 +102,14 @@ fun UserList(
|
|||
title = user.name ?: user.id.toString(),
|
||||
dialogItems =
|
||||
listOf(
|
||||
DialogItem("Switch", R.string.fa_arrow_left_arrow_right) {
|
||||
DialogItem(
|
||||
stringResource(R.string.switch_user),
|
||||
R.string.fa_arrow_left_arrow_right,
|
||||
) {
|
||||
onSwitchUser.invoke(user)
|
||||
},
|
||||
DialogItem(
|
||||
"Delete",
|
||||
stringResource(R.string.delete),
|
||||
Icons.Default.Delete,
|
||||
Color.Red.copy(alpha = .8f),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import com.github.damontecres.wholphin.ui.main.HomeSection
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
|
||||
val supportedHomeSection =
|
||||
setOf(
|
||||
HomeSection.LATEST_MEDIA,
|
||||
HomeSection.NEXT_UP,
|
||||
HomeSection.RESUME,
|
||||
)
|
||||
|
||||
val supportItemKinds =
|
||||
setOf(
|
||||
BaseItemKind.MOVIE,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class CrashReportSender : ReportSender {
|
|||
val api =
|
||||
createJellyfin {
|
||||
this.context = context
|
||||
clientInfo = AppModule.clientInfo()
|
||||
clientInfo = AppModule.clientInfo(context)
|
||||
deviceInfo = AppModule.deviceInfo(context)
|
||||
apiClientFactory = okHttpFactory
|
||||
socketConnectionFactory = okHttpFactory
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.github.damontecres.wholphin.R
|
||||
|
|
@ -11,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.chooseStream
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import java.time.LocalDate
|
||||
|
|
@ -164,3 +166,15 @@ fun formatBytes(
|
|||
}
|
||||
return String.format(Locale.getDefault(), "%.2f%s", count, suffixes[unit])
|
||||
}
|
||||
|
||||
@get:StringRes
|
||||
val MediaSegmentType.stringRes: Int
|
||||
get() =
|
||||
when (this) {
|
||||
MediaSegmentType.UNKNOWN -> R.string.unknown
|
||||
MediaSegmentType.COMMERCIAL -> R.string.commercial
|
||||
MediaSegmentType.PREVIEW -> R.string.preview
|
||||
MediaSegmentType.RECAP -> R.string.recap
|
||||
MediaSegmentType.OUTRO -> R.string.outro
|
||||
MediaSegmentType.INTRO -> R.string.intro
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pref_key_update_last_check_threshold" translatable="false">preference.update.threshold</string>
|
||||
<string name="pref_key_update_last_check" translatable="false">preference.update.lastTimestamp</string>
|
||||
|
||||
<string name="ac3_supported">Device supports AC3/Dolby Digital</string>
|
||||
<string name="advanced_settings">Advanced Settings</string>
|
||||
<string name="advanced_ui">Advanced UI</string>
|
||||
<string name="app_theme">Application Theme</string>
|
||||
<string name="auto_check_for_updates">Automatically check for updates</string>
|
||||
<string name="auto_play_next_delay">Delay before playing next up</string>
|
||||
<string name="auto_play_next">Auto play next up</string>
|
||||
<string name="check_for_updates">Check for updates</string>
|
||||
<string name="combine_continue_next_summary">Applies to TV Series only</string>
|
||||
<string name="combine_continue_next"><![CDATA[Combine Continue Watching & Next Up]]></string>
|
||||
<string name="direct_play_ass">Direct play ASS subtitles</string>
|
||||
<string name="direct_play_pgs">Direct play PGS subtitles</string>
|
||||
<string name="downmix_stereo">Always downmix to stereo</string>
|
||||
<string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string>
|
||||
<string name="global_content_scale">Default content scale</string>
|
||||
<string name="install_update">Install update</string>
|
||||
<string name="installed_version">Installed version</string>
|
||||
<string name="max_homepage_items">Max items on home page rows</string>
|
||||
<string name="nav_drawer_pins_summary">Choose the default items to show, others will be hidden</string>
|
||||
<string name="nav_drawer_pins">Customize Navigation Drawer Items</string>
|
||||
<string name="nav_drawer_switch_on_focus_summary_off">Click to switch pages</string>
|
||||
<string name="nav_drawer_switch_on_focus">Switch nav drawer pages on focus</string>
|
||||
<string name="pass_out_protection">Passout Protection</string>
|
||||
<string name="play_theme_music">Play theme music</string>
|
||||
<string name="playback_overrides">Playback overrides</string>
|
||||
<string name="remember_selected_tab">Remember selected tabs</string>
|
||||
<string name="rewatch_next_up">Enable rewatching in next up</string>
|
||||
<string name="seek_bar_steps">Seek bar steps</string>
|
||||
<string name="send_app_logs_summary">Useful for debugging</string>
|
||||
<string name="send_app_logs">Send app logs to current server</string>
|
||||
<string name="send_crash_reports_summary">Will try to send to last connected server</string>
|
||||
<string name="send_crash_reports">Send Crash Reports</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="skip_back_on_resume_preference">Skip back when resuming playback</string>
|
||||
<string name="skip_back_preference">Skip back</string>
|
||||
<string name="skip_commercials_behavior">Skip commercials behavior</string>
|
||||
<string name="skip_forward_preference">Skip forward</string>
|
||||
<string name="skip_intro_behavior">Skip intro behavior</string>
|
||||
<string name="skip_outro_behavior">Skip outro behavior</string>
|
||||
<string name="skip_previews_behavior">Skip previews behavior</string>
|
||||
<string name="skip_recap_behavior">Skip recap behavior</string>
|
||||
<string name="update_available">Update available</string>
|
||||
<string name="update_url_summary">URL used to check for app updates</string>
|
||||
<string name="update_url">Update URL</string>
|
||||
|
||||
<string-array name="theme_song_volume">
|
||||
<item>Disabled</item>
|
||||
<item>Lowest</item>
|
||||
|
|
|
|||
|
|
@ -1,109 +1,157 @@
|
|||
<resources>
|
||||
<string name="app_name">Wholphin</string>
|
||||
<string name="app_name" translatable="false">Wholphin</string>
|
||||
<string name="app_name_long" translatable="false">Wholphin</string>
|
||||
|
||||
<string name="home_section_latest_media">Recently added media</string>
|
||||
<string name="home_section_library">My media</string>
|
||||
<string name="home_section_library_small">My media (small)</string>
|
||||
<string name="home_section_resume">Continue watching</string>
|
||||
<string name="home_section_resume_audio">Continue listening</string>
|
||||
<string name="home_section_resume_book">Continue reading</string>
|
||||
<string name="home_section_active_recordings">Active recordings</string>
|
||||
<string name="home_section_next_up">Next up</string>
|
||||
<string name="home_section_livetv">Live TV</string>
|
||||
<string name="home_section_none">None</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="restart">Restart</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="more">More</string>
|
||||
<string name="about">About</string>
|
||||
<string name="active_recordings">Active Recordings</string>
|
||||
<string name="add_favorite">Favorite</string>
|
||||
<string name="add_server">Add Server</string>
|
||||
<string name="add_user">Add User</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="birthplace">Birthplace</string>
|
||||
<string name="bitrate">Bitrate</string>
|
||||
<string name="born">Born</string>
|
||||
<string name="cancel_recording">Cancel Recording</string>
|
||||
<string name="cancel_series_recording">Cancel Series Recording</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="chapters">Chapters</string>
|
||||
<string name="choose_stream">Choose %1$s</string>
|
||||
<string name="clear_image_cache">Clear image cache</string>
|
||||
<string name="collection">Collection</string>
|
||||
<string name="collections">Collections</string>
|
||||
<string name="commercial">Commercial</string>
|
||||
<string name="community_rating">Community Rating</string>
|
||||
<string name="compose_error_message_title">An error occurred! Press the button to send logs to your server.</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="continue_watching">Continue watching</string>
|
||||
<string name="critic_rating">Critic Rating</string>
|
||||
<string name="decimal_seconds">%.1f seconds</string>
|
||||
<string name="default_track">Default</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="died">Died</string>
|
||||
<string name="directed_by">Directed by %1$s</string>
|
||||
<string name="director">Director</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="discovered_servers">Discovered Servers</string>
|
||||
<string name="download_and_update"><![CDATA[Download & Update]]></string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="enabled">Enabled</string>
|
||||
<string name="enter_server_url">Enter Server IP or URL including port</string>
|
||||
<string name="episodes">Episodes</string>
|
||||
<string name="error_loading_collection">Error loading collection %1$s</string>
|
||||
<string name="external_track">External</string>
|
||||
<string name="favorites">Favorites</string>
|
||||
<string name="file_size">Size</string>
|
||||
<string name="forced_track">Forced</string>
|
||||
<string name="genres">Genres</string>
|
||||
<string name="go_to_series">Go to series</string>
|
||||
<string name="go_to">Go To</string>
|
||||
<string name="hide_controller_timeout">Hide playback controls</string>
|
||||
<string name="hide_debug_info">Hide debug info</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="home">Home</string>
|
||||
<string name="immediate">Immediate</string>
|
||||
<string name="intro">Intro</string>
|
||||
<string name="jump_letters">#ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||
<string name="library">Library</string>
|
||||
<string name="license_info">License information</string>
|
||||
<string name="live_tv">Live TV</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="mark_entire_series_as_played">Mark entire series as played?</string>
|
||||
<string name="mark_entire_series_as_unplayed">Mark entire series as unplayed?</string>
|
||||
<string name="mark_unwatched">Mark as unwatched</string>
|
||||
<string name="mark_watched">Mark as watched</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="app_name_long">Wholphin</string>
|
||||
<string name="ui_interface">Interface</string>
|
||||
<string name="playback">Playback</string>
|
||||
<string name="about">About</string>
|
||||
<string name="advanced_settings">Advanced Settings</string>
|
||||
<string name="advanced_ui">Advanced UI</string>
|
||||
<string name="install_update">Install update</string>
|
||||
<string name="installed_version">Installed version</string>
|
||||
<string name="show">Show</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="enabled">Enabled</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="skip_forward_preference">Skip forward</string>
|
||||
<string name="skip_back_preference">Skip back</string>
|
||||
<string name="hide_controller_timeout">Hide playback controls</string>
|
||||
<string name="seek_bar_steps">Seek bar steps</string>
|
||||
<string name="max_homepage_items">Max items on home page rows</string>
|
||||
<string name="rewatch_next_up">Enable rewatching in next up</string>
|
||||
<string name="studios">Studios</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="genres">Genres</string>
|
||||
<string name="play_theme_music">Play theme music</string>
|
||||
<string name="license_info">License information</string>
|
||||
<string name="max_bitrate">Max bitrate</string>
|
||||
<string name="more_like_this">More like this</string>
|
||||
<string name="more">More</string>
|
||||
<string name="movies">Movies</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="next_up">Next Up</string>
|
||||
<string name="no_data">No data</string>
|
||||
<string name="no_results">No results</string>
|
||||
<string name="no_scheduled_recordings">No scheduled recordings</string>
|
||||
<string name="no_update_available">No update available</string>
|
||||
<string name="none">None</string>
|
||||
<string name="outro">Outro</string>
|
||||
<string name="path">Path</string>
|
||||
<string name="people">People</string>
|
||||
<string name="play_count">Play Count</string>
|
||||
<string name="play_from_here">Play from here</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="playback_debug_info">Show playback debug info</string>
|
||||
<string name="auto_play_next_delay">Delay before playing next up</string>
|
||||
<string name="auto_play_next">Auto play next up</string>
|
||||
|
||||
|
||||
<string name="sort_by_name">Name</string>
|
||||
<string name="sort_by_random">Random</string>
|
||||
<string name="sort_by_date_episode_added">Date Episode Added</string>
|
||||
<string name="playback_speed">Playback Speed</string>
|
||||
<string name="playback">Playback</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
<string name="preview">Preview</string>
|
||||
<string name="profile_specific_settings">User Profile Settings</string>
|
||||
<string name="queue">Queue</string>
|
||||
<string name="recap">Recap</string>
|
||||
<string name="recently_added_in">Recently added in %1$s</string>
|
||||
<string name="recently_added">Recently added</string>
|
||||
<string name="recently_recorded">Recently Recorded</string>
|
||||
<string name="recently_released">Recently Released</string>
|
||||
<string name="recommended">Recommended</string>
|
||||
<string name="record_program">Record Program</string>
|
||||
<string name="record_series">Record Series</string>
|
||||
<string name="remove_favorite">Unfavorite</string>
|
||||
<string name="restart">Restart</string>
|
||||
<string name="resume">Resume</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="search_and_download"><![CDATA[Search & Download]]></string>
|
||||
<string name="search">Search</string>
|
||||
<string name="searching">Searching…</string>
|
||||
<string name="select_server">Select Server</string>
|
||||
<string name="select_user">Select User</string>
|
||||
<string name="show_debug_info">Show debug info</string>
|
||||
<string name="show_next_up_when">Show next up</string>
|
||||
<string name="show">Show</string>
|
||||
<string name="shuffle">Shuffle</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="sort_by_date_added">Date Added</string>
|
||||
<string name="sort_by_date_episode_added">Date Episode Added</string>
|
||||
<string name="sort_by_date_played">Date Played</string>
|
||||
<string name="sort_by_date_released">Date Released</string>
|
||||
<string name="skip_back_on_resume_preference">Skip back when resuming playback</string>
|
||||
<string name="pref_key_update_last_check_threshold">preference.update.threshold</string>
|
||||
<string name="pref_key_update_last_check">preference.update.lastTimestamp</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="remember_selected_tab">Remember selected tabs</string>
|
||||
<string name="director">Director</string>
|
||||
<string name="max_bitrate">Max bitrate</string>
|
||||
<string name="app_theme">Application Theme</string>
|
||||
<string name="skip_recap_behavior">Skip recap behavior</string>
|
||||
<string name="skip_previews_behavior">Skip previews behavior</string>
|
||||
<string name="skip_comercials_behavior">Skip commercials behavior</string>
|
||||
<string name="skip_outro_behavior">Skip outro behavior</string>
|
||||
<string name="skip_intro_behavior">Skip intro behavior</string>
|
||||
<string name="check_for_updates">Check for updates</string>
|
||||
<string name="auto_check_for_updates">Automatically check for updates</string>
|
||||
<string name="update_url">Update URL</string>
|
||||
<string name="update_url_summary">URL used to check for app updates</string>
|
||||
<string name="updates">Updates</string>
|
||||
<string name="no_update_available">No update available</string>
|
||||
<string name="clear_image_cache">Clear image cache</string>
|
||||
<string name="shuffle">Shuffle</string>
|
||||
<string name="combine_continue_next"><![CDATA[Combine Continue Watching & Next Up]]></string>
|
||||
<string name="ac3_supported">Device supports AC3/Dolby Digital</string>
|
||||
<string name="downmix_stereo">Always downmix to stereo</string>
|
||||
<string name="playback_overrides">Playback overrides</string>
|
||||
<string name="direct_play_ass">Direct play ASS subtitles</string>
|
||||
<string name="direct_play_pgs">Direct play PGS subtitles</string>
|
||||
<string name="sort_by_name">Name</string>
|
||||
<string name="sort_by_random">Random</string>
|
||||
<string name="studios">Studios</string>
|
||||
<string name="submit">Submit</string>
|
||||
<string name="subtitle_download_too_long">Download is taking a long time, you may need to restart playback</string>
|
||||
<string name="subtitle">Subtitle</string>
|
||||
<string name="subtitles">Subtitles</string>
|
||||
<string name="suggestions">Suggestions</string>
|
||||
<string name="switch_servers">Switch servers</string>
|
||||
<string name="switch_user">Switch</string>
|
||||
<string name="top_unwatched">Top Rated Unwatched</string>
|
||||
<string name="trailer">Trailer</string>
|
||||
<string name="trailers">Trailers</string>
|
||||
<string name="pass_out_protection">Passout Protection</string>
|
||||
<string name="more_like_this">More like this</string>
|
||||
<string name="remove_favorite">Unfavorite</string>
|
||||
<string name="add_favorite">Favorite</string>
|
||||
<string name="favorites">Favorites</string>
|
||||
<string name="nav_drawer_pins">Customize Navigation Drawer Items</string>
|
||||
<string name="profile_specific_settings">User Profile Settings</string>
|
||||
<string name="nav_drawer_pins_summary">Choose the default items to show, others will be hidden</string>
|
||||
<string name="combine_continue_next_summary">Applies to TV Series only</string>
|
||||
<string name="send_crash_reports">Send Crash Reports</string>
|
||||
<string name="send_crash_reports_summary">Will try to send to last connected server</string>
|
||||
<string name="send_app_logs">Send app logs to current server</string>
|
||||
<string name="send_app_logs_summary">Useful for debugging</string>
|
||||
<string name="global_content_scale">Default content scale</string>
|
||||
<string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string>
|
||||
<string name="nav_drawer_switch_on_focus">Switch nav drawer pages on focus</string>
|
||||
<string name="nav_drawer_switch_on_focus_summary_off">Click to switch pages</string>
|
||||
<string name="show_next_up_when">Show next up</string>
|
||||
<string name="next_up">Next Up</string>
|
||||
<string name="community_rating">Community Rating</string>
|
||||
<string name="critic_rating">Critic Rating</string>
|
||||
<string name="play_count">Play Count</string>
|
||||
<string name="tv_dvr_schedule">DVR Schedule</string>
|
||||
<string name="tv_guide">Guide</string>
|
||||
<string name="tv_season">Season</string>
|
||||
<string name="tv_seasons">Seasons</string>
|
||||
<string name="tv_shows">TV Shows</string>
|
||||
<string name="ui_interface">Interface</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="updates">Updates</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="video_scale">Video Scale</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="watch_live">Watch live</string>
|
||||
<string name="years_old">%1$d years old</string>
|
||||
|
||||
<plurals name="downloads">
|
||||
<item quantity="zero">%d downloads</item>
|
||||
<item quantity="one">%d download</item>
|
||||
<item quantity="other">%d downloads</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="zero">%d hours</item>
|
||||
<item quantity="one">%d hour</item>
|
||||
<item quantity="other">%d hours</item>
|
||||
</plurals>
|
||||
<plurals name="seconds">
|
||||
<item quantity="zero">%d seconds</item>
|
||||
<item quantity="one">%d second</item>
|
||||
<item quantity="other">%d seconds</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue