mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into fea/libass-android-support
This commit is contained in:
commit
a126869a09
69 changed files with 3079 additions and 870 deletions
|
|
@ -53,6 +53,7 @@ import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
|
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
||||||
import com.github.damontecres.wholphin.util.DebugLogTree
|
import com.github.damontecres.wholphin.util.DebugLogTree
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
@ -92,9 +93,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var updateChecker: UpdateChecker
|
lateinit var updateChecker: UpdateChecker
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var appUpgradeHandler: AppUpgradeHandler
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver
|
lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver
|
||||||
|
|
||||||
|
|
@ -135,11 +133,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
instance = this
|
instance = this
|
||||||
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
||||||
lifecycle.addObserver(playbackLifecycleObserver)
|
lifecycle.addObserver(playbackLifecycleObserver)
|
||||||
if (savedInstanceState == null) {
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
appUpgradeHandler.copySubfont(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModel.serverRepository.currentUser.observe(this) { user ->
|
viewModel.serverRepository.currentUser.observe(this) { user ->
|
||||||
if (user?.hasPin == true) {
|
if (user?.hasPin == true) {
|
||||||
window?.setFlags(
|
window?.setFlags(
|
||||||
|
|
@ -211,6 +205,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
true,
|
true,
|
||||||
appThemeColors = appPreferences.interfacePreferences.appThemeColors,
|
appThemeColors = appPreferences.interfacePreferences.appThemeColors,
|
||||||
) {
|
) {
|
||||||
|
ProvideLocalClock {
|
||||||
val requestedDestination =
|
val requestedDestination =
|
||||||
remember(intent) {
|
remember(intent) {
|
||||||
intent?.let(::extractDestination) ?: Destination.Home()
|
intent?.let(::extractDestination) ?: Destination.Home()
|
||||||
|
|
@ -229,6 +224,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
if (screensaverService.state.value.show) {
|
if (screensaverService.state.value.show) {
|
||||||
|
|
@ -246,7 +242,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
Timber.d("onResume")
|
Timber.d("onResume")
|
||||||
lifecycleScope.launchDefault {
|
lifecycleScope.launchDefault {
|
||||||
screensaverService.pulse()
|
screensaverService.pulse()
|
||||||
appUpgradeHandler.run()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,10 +376,13 @@ class MainActivityViewModel
|
||||||
private val navigationManager: SetupNavigationManager,
|
private val navigationManager: SetupNavigationManager,
|
||||||
private val deviceProfileService: DeviceProfileService,
|
private val deviceProfileService: DeviceProfileService,
|
||||||
private val backdropService: BackdropService,
|
private val backdropService: BackdropService,
|
||||||
|
private val appUpgradeHandler: AppUpgradeHandler,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
fun appStart() {
|
fun appStart() {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
|
appUpgradeHandler.run()
|
||||||
|
appUpgradeHandler.copySubfont(false)
|
||||||
val prefs =
|
val prefs =
|
||||||
preferences.data.firstOrNull() ?: AppPreferences.getDefaultInstance()
|
preferences.data.firstOrNull() ?: AppPreferences.getDefaultInstance()
|
||||||
val userHasPin = serverRepository.currentUser.value?.hasPin == true
|
val userHasPin = serverRepository.currentUser.value?.hasPin == true
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ fun MainContent(
|
||||||
backdropService.clearBackdrop()
|
backdropService.clearBackdrop()
|
||||||
}
|
}
|
||||||
val current = key.current
|
val current = key.current
|
||||||
ProvideLocalClock {
|
|
||||||
val preferences =
|
val preferences =
|
||||||
remember(appPreferences) {
|
remember(appPreferences) {
|
||||||
UserPreferences(appPreferences)
|
UserPreferences(appPreferences)
|
||||||
|
|
@ -133,7 +132,6 @@ fun MainContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val screenSaverState by screensaverService.state.collectAsState()
|
val screenSaverState by screensaverService.state.collectAsState()
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -18,14 +20,19 @@ import androidx.savedstate.SavedStateRegistry
|
||||||
import androidx.savedstate.SavedStateRegistryController
|
import androidx.savedstate.SavedStateRegistryController
|
||||||
import androidx.savedstate.SavedStateRegistryOwner
|
import androidx.savedstate.SavedStateRegistryOwner
|
||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.services.ScreensaverService
|
import com.github.damontecres.wholphin.services.ScreensaverService
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.ui.components.AppScreensaverContent
|
import com.github.damontecres.wholphin.ui.components.AppScreensaverContent
|
||||||
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
@ -33,11 +40,14 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||||
class WholphinDreamService :
|
class WholphinDreamService :
|
||||||
DreamService(),
|
DreamService(),
|
||||||
SavedStateRegistryOwner {
|
SavedStateRegistryOwner {
|
||||||
|
@Inject
|
||||||
|
lateinit var serverRepository: ServerRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var screensaverService: ScreensaverService
|
lateinit var screensaverService: ScreensaverService
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var userPreferencesService: UserPreferencesService
|
lateinit var preferencesDataStore: DataStore<AppPreferences>
|
||||||
|
|
||||||
private val lifecycleRegistry = LifecycleRegistry(this)
|
private val lifecycleRegistry = LifecycleRegistry(this)
|
||||||
|
|
||||||
|
|
@ -54,6 +64,12 @@ class WholphinDreamService :
|
||||||
|
|
||||||
savedStateRegistryController.performRestore(null)
|
savedStateRegistryController.performRestore(null)
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||||
|
lifecycleScope.launchDefault {
|
||||||
|
if (serverRepository.current.value == null) {
|
||||||
|
val prefs = preferencesDataStore.data.first()
|
||||||
|
serverRepository.restoreSession(prefs.currentServerId.toUUIDOrNull(), prefs.currentUserId.toUUIDOrNull())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
|
|
@ -64,15 +80,16 @@ class WholphinDreamService :
|
||||||
setViewTreeLifecycleOwner(this@WholphinDreamService)
|
setViewTreeLifecycleOwner(this@WholphinDreamService)
|
||||||
setViewTreeSavedStateRegistryOwner(this@WholphinDreamService)
|
setViewTreeSavedStateRegistryOwner(this@WholphinDreamService)
|
||||||
setContent {
|
setContent {
|
||||||
var prefs by remember { mutableStateOf<UserPreferences?>(null) }
|
val user by serverRepository.currentUser.observeAsState()
|
||||||
|
if (user != null) {
|
||||||
|
var prefs by remember { mutableStateOf<AppPreferences?>(null) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
userPreferencesService.flow.collectLatest { prefs = it }
|
preferencesDataStore.data.collectLatest { prefs = it }
|
||||||
}
|
}
|
||||||
prefs?.let { prefs ->
|
prefs?.let { prefs ->
|
||||||
WholphinTheme(appThemeColors = prefs.appPreferences.interfacePreferences.appThemeColors) {
|
WholphinTheme(appThemeColors = prefs.interfacePreferences.appThemeColors) {
|
||||||
ProvideLocalClock {
|
ProvideLocalClock {
|
||||||
val screensaverPrefs =
|
val screensaverPrefs = prefs.interfacePreferences.screensaverPreference
|
||||||
prefs.appPreferences.interfacePreferences.screensaverPreference
|
|
||||||
val currentItem by itemFlow.collectAsState(null)
|
val currentItem by itemFlow.collectAsState(null)
|
||||||
AppScreensaverContent(
|
AppScreensaverContent(
|
||||||
currentItem = currentItem,
|
currentItem = currentItem,
|
||||||
|
|
@ -85,6 +102,7 @@ class WholphinDreamService :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,17 @@
|
||||||
|
|
||||||
package com.github.damontecres.wholphin.data.model
|
package com.github.damontecres.wholphin.data.model
|
||||||
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.CreditCast
|
import androidx.compose.runtime.Stable
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.CreditCrew
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response
|
import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.TvResult
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response
|
import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response
|
||||||
import com.github.damontecres.wholphin.services.SeerrSearchResult
|
|
||||||
import com.github.damontecres.wholphin.ui.detail.CardGridItem
|
import com.github.damontecres.wholphin.ui.detail.CardGridItem
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.toLocalDate
|
|
||||||
import com.github.damontecres.wholphin.util.LocalDateSerializer
|
import com.github.damontecres.wholphin.util.LocalDateSerializer
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
@ -74,6 +66,7 @@ enum class SeerrAvailability(
|
||||||
/**
|
/**
|
||||||
* An item provided by a discovery service (ie Seerr). It may exist on the JF server as well.
|
* An item provided by a discovery service (ie Seerr). It may exist on the JF server as well.
|
||||||
*/
|
*/
|
||||||
|
@Stable
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DiscoverItem(
|
data class DiscoverItem(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
@ -83,17 +76,14 @@ data class DiscoverItem(
|
||||||
val overview: String?,
|
val overview: String?,
|
||||||
val availability: SeerrAvailability,
|
val availability: SeerrAvailability,
|
||||||
@Serializable(LocalDateSerializer::class) val releaseDate: LocalDate?,
|
@Serializable(LocalDateSerializer::class) val releaseDate: LocalDate?,
|
||||||
val posterPath: String?,
|
val posterUrl: String?,
|
||||||
val backdropPath: String?,
|
val backDropUrl: String?,
|
||||||
val jellyfinItemId: UUID?,
|
val jellyfinItemId: UUID?,
|
||||||
) : CardGridItem {
|
) : CardGridItem {
|
||||||
override val gridId: String get() = id.toString()
|
override val gridId: String get() = id.toString()
|
||||||
override val playable: Boolean = false
|
override val playable: Boolean = false
|
||||||
override val sortName: String get() = title ?: ""
|
override val sortName: String get() = title ?: ""
|
||||||
|
|
||||||
val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h1080_multi_faces$it" }
|
|
||||||
val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
|
|
||||||
|
|
||||||
val destination: Destination
|
val destination: Destination
|
||||||
get() {
|
get() {
|
||||||
val jfType =
|
val jfType =
|
||||||
|
|
@ -112,103 +102,6 @@ data class DiscoverItem(
|
||||||
Destination.DiscoveredItem(this)
|
Destination.DiscoveredItem(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(movie: MovieResult) : this(
|
|
||||||
id = movie.id,
|
|
||||||
type = SeerrItemType.MOVIE,
|
|
||||||
title = movie.title,
|
|
||||||
subtitle = null,
|
|
||||||
overview = movie.overview,
|
|
||||||
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(movie.releaseDate),
|
|
||||||
posterPath = movie.posterPath,
|
|
||||||
backdropPath = movie.backdropPath,
|
|
||||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(movie: MovieDetails) : this(
|
|
||||||
id = movie.id ?: -1,
|
|
||||||
type = SeerrItemType.MOVIE,
|
|
||||||
title = movie.title,
|
|
||||||
subtitle = null,
|
|
||||||
overview = movie.overview,
|
|
||||||
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(movie.releaseDate),
|
|
||||||
posterPath = movie.posterPath,
|
|
||||||
backdropPath = movie.backdropPath,
|
|
||||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(tv: TvResult) : this(
|
|
||||||
id = tv.id!!,
|
|
||||||
type = SeerrItemType.TV,
|
|
||||||
title = tv.name,
|
|
||||||
subtitle = null,
|
|
||||||
overview = tv.overview,
|
|
||||||
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(tv.firstAirDate),
|
|
||||||
posterPath = tv.posterPath,
|
|
||||||
backdropPath = tv.backdropPath,
|
|
||||||
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(tv: TvDetails) : this(
|
|
||||||
id = tv.id!!,
|
|
||||||
type = SeerrItemType.TV,
|
|
||||||
title = tv.name,
|
|
||||||
subtitle = null,
|
|
||||||
overview = tv.overview,
|
|
||||||
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(tv.firstAirDate),
|
|
||||||
posterPath = tv.posterPath,
|
|
||||||
backdropPath = tv.backdropPath,
|
|
||||||
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(search: SeerrSearchResult) : this(
|
|
||||||
id = search.id,
|
|
||||||
type = SeerrItemType.fromString(search.mediaType),
|
|
||||||
title = search.title ?: search.name,
|
|
||||||
subtitle = null,
|
|
||||||
overview = search.overview,
|
|
||||||
availability =
|
|
||||||
SeerrAvailability.from(search.mediaInfo?.status)
|
|
||||||
?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate),
|
|
||||||
posterPath = search.posterPath,
|
|
||||||
backdropPath = search.backdropPath,
|
|
||||||
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(credit: CreditCast) : this(
|
|
||||||
id = credit.id!!,
|
|
||||||
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
|
||||||
title = credit.name ?: credit.title,
|
|
||||||
subtitle = credit.character,
|
|
||||||
overview = credit.overview,
|
|
||||||
availability =
|
|
||||||
SeerrAvailability.from(credit.mediaInfo?.status)
|
|
||||||
?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(credit.firstAirDate),
|
|
||||||
posterPath = credit.posterPath ?: credit.profilePath,
|
|
||||||
backdropPath = credit.backdropPath,
|
|
||||||
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(credit: CreditCrew) : this(
|
|
||||||
id = credit.id!!,
|
|
||||||
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
|
||||||
title = credit.name ?: credit.title,
|
|
||||||
subtitle = credit.job,
|
|
||||||
overview = credit.overview,
|
|
||||||
availability =
|
|
||||||
SeerrAvailability.from(credit.mediaInfo?.status)
|
|
||||||
?: SeerrAvailability.UNKNOWN,
|
|
||||||
releaseDate = toLocalDate(credit.firstAirDate),
|
|
||||||
posterPath = credit.posterPath ?: credit.profilePath,
|
|
||||||
backdropPath = credit.backdropPath,
|
|
||||||
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DiscoverRating(
|
data class DiscoverRating(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.core.content.edit
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.damontecres.wholphin.WholphinApplication
|
import com.github.damontecres.wholphin.WholphinApplication
|
||||||
|
import com.github.damontecres.wholphin.data.SeerrServerDao
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.ScreensaverPreference
|
import com.github.damontecres.wholphin.preferences.ScreensaverPreference
|
||||||
|
|
@ -21,6 +22,7 @@ import com.github.damontecres.wholphin.preferences.updateScreensaverPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
||||||
|
import com.github.damontecres.wholphin.ui.setup.seerr.migrateSeerrUrl
|
||||||
import com.github.damontecres.wholphin.util.Version
|
import com.github.damontecres.wholphin.util.Version
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
|
@ -35,9 +37,14 @@ class AppUpgradeHandler
|
||||||
constructor(
|
constructor(
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
private val appPreferences: DataStore<AppPreferences>,
|
private val appPreferences: DataStore<AppPreferences>,
|
||||||
|
private val seerrServerDao: SeerrServerDao,
|
||||||
) {
|
) {
|
||||||
suspend fun run() {
|
suspend fun run() {
|
||||||
val pkgInfo = WholphinApplication.instance.packageManager.getPackageInfo(WholphinApplication.instance.packageName, 0)
|
val pkgInfo =
|
||||||
|
WholphinApplication.instance.packageManager.getPackageInfo(
|
||||||
|
WholphinApplication.instance.packageName,
|
||||||
|
0,
|
||||||
|
)
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val previousVersion = prefs.getString(VERSION_NAME_CURRENT_KEY, null)
|
val previousVersion = prefs.getString(VERSION_NAME_CURRENT_KEY, null)
|
||||||
val previousVersionCode = prefs.getLong(VERSION_CODE_CURRENT_KEY, -1)
|
val previousVersionCode = prefs.getLong(VERSION_CODE_CURRENT_KEY, -1)
|
||||||
|
|
@ -62,14 +69,16 @@ class AppUpgradeHandler
|
||||||
try {
|
try {
|
||||||
copySubfont(true)
|
copySubfont(true)
|
||||||
upgradeApp(
|
upgradeApp(
|
||||||
context,
|
Version.fromString(previousVersion ?: "0.0.0"),
|
||||||
Version.Companion.fromString(previousVersion ?: "0.0.0"),
|
Version.fromString(newVersion),
|
||||||
Version.Companion.fromString(newVersion),
|
|
||||||
appPreferences,
|
appPreferences,
|
||||||
)
|
)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex, "Exception during app upgrade")
|
Timber.e(ex, "Exception during app upgrade")
|
||||||
}
|
}
|
||||||
|
Timber.i("App upgrade complete")
|
||||||
|
} else {
|
||||||
|
Timber.d("No app update needed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,14 +109,12 @@ class AppUpgradeHandler
|
||||||
const val VERSION_NAME_CURRENT_KEY = "version.current.name"
|
const val VERSION_NAME_CURRENT_KEY = "version.current.name"
|
||||||
const val VERSION_CODE_CURRENT_KEY = "version.current.code"
|
const val VERSION_CODE_CURRENT_KEY = "version.current.code"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun upgradeApp(
|
suspend fun upgradeApp(
|
||||||
context: Context,
|
|
||||||
previous: Version,
|
previous: Version,
|
||||||
current: Version,
|
current: Version,
|
||||||
appPreferences: DataStore<AppPreferences>,
|
appPreferences: DataStore<AppPreferences>,
|
||||||
) {
|
) {
|
||||||
if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) {
|
if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) {
|
||||||
appPreferences.updateData {
|
appPreferences.updateData {
|
||||||
it.updatePlaybackOverrides {
|
it.updatePlaybackOverrides {
|
||||||
|
|
@ -226,7 +233,8 @@ suspend fun upgradeApp(
|
||||||
subtitlesPreferences
|
subtitlesPreferences
|
||||||
.toBuilder()
|
.toBuilder()
|
||||||
.apply {
|
.apply {
|
||||||
imageSubtitleOpacity = SubtitleSettings.ImageOpacity.defaultValue.toInt()
|
imageSubtitleOpacity =
|
||||||
|
SubtitleSettings.ImageOpacity.defaultValue.toInt()
|
||||||
}.build()
|
}.build()
|
||||||
// Copy current subtitle prefs as HDR ones
|
// Copy current subtitle prefs as HDR ones
|
||||||
hdrSubtitlesPreferences = subtitlesPreferences.toBuilder().build()
|
hdrSubtitlesPreferences = subtitlesPreferences.toBuilder().build()
|
||||||
|
|
@ -250,7 +258,7 @@ suspend fun upgradeApp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previous.isEqualOrBefore(Version.fromString("0.5.0-6-g0"))) {
|
if (previous.isEqualOrBefore(Version.fromString("0.5.3-0-g0"))) {
|
||||||
appPreferences.updateData {
|
appPreferences.updateData {
|
||||||
it.updateScreensaverPreferences {
|
it.updateScreensaverPreferences {
|
||||||
startDelay = ScreensaverPreference.DEFAULT_START_DELAY
|
startDelay = ScreensaverPreference.DEFAULT_START_DELAY
|
||||||
|
|
@ -263,4 +271,14 @@ suspend fun upgradeApp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (previous.isEqualOrBefore(Version.fromString("0.5.4-0-g0"))) {
|
||||||
|
seerrServerDao.getServers().forEach {
|
||||||
|
val server = it.server
|
||||||
|
seerrServerDao.updateServer(
|
||||||
|
server.copy(url = migrateSeerrUrl(server.url)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,13 @@ class ScreensaverService
|
||||||
if (pager.isEmpty()) {
|
if (pager.isEmpty()) {
|
||||||
emit(null)
|
emit(null)
|
||||||
} else {
|
} else {
|
||||||
|
val duration =
|
||||||
|
userPreferencesService
|
||||||
|
.getCurrent()
|
||||||
|
.appPreferences
|
||||||
|
.interfacePreferences.screensaverPreference.duration.milliseconds
|
||||||
while (true) {
|
while (true) {
|
||||||
|
try {
|
||||||
val item = pager.getBlocking(index)
|
val item = pager.getBlocking(index)
|
||||||
Timber.v("Next index=%s, item=%s", index, item?.id)
|
Timber.v("Next index=%s, item=%s", index, item?.id)
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
|
|
@ -181,7 +187,6 @@ class ScreensaverService
|
||||||
}
|
}
|
||||||
val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO)
|
val logoUrl = imageUrlService.getItemImageUrl(item, ImageType.LOGO)
|
||||||
if (backdropUrl != null) {
|
if (backdropUrl != null) {
|
||||||
try {
|
|
||||||
context.imageLoader
|
context.imageLoader
|
||||||
.enqueue(
|
.enqueue(
|
||||||
ImageRequest
|
ImageRequest
|
||||||
|
|
@ -191,18 +196,17 @@ class ScreensaverService
|
||||||
).job
|
).job
|
||||||
.await()
|
.await()
|
||||||
emit(CurrentItem(item, backdropUrl, logoUrl, title ?: ""))
|
emit(CurrentItem(item, backdropUrl, logoUrl, title ?: ""))
|
||||||
} catch (_: CancellationException) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val duration =
|
|
||||||
userPreferencesService
|
|
||||||
.getCurrent()
|
|
||||||
.appPreferences
|
|
||||||
.interfacePreferences.screensaverPreference.duration.milliseconds
|
|
||||||
delay(duration)
|
delay(duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (_: CancellationException) {
|
||||||
|
break
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.e(ex, "Error fetching next item")
|
||||||
|
delay(duration)
|
||||||
|
}
|
||||||
index++
|
index++
|
||||||
|
if (index > pager.lastIndex) index = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.Default).cancellable()
|
}.flowOn(Dispatchers.Default).cancellable()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.services
|
||||||
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.SeerrApiClient
|
import com.github.damontecres.wholphin.api.seerr.SeerrApiClient
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,6 +25,6 @@ class SeerrApi(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
apiKey: String?,
|
apiKey: String?,
|
||||||
) {
|
) {
|
||||||
api = SeerrApiClient(baseUrl, apiKey, okHttpClient)
|
api = SeerrApiClient(createSeerrApiUrl(baseUrl), apiKey, okHttpClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,19 @@ import com.github.damontecres.wholphin.data.model.SeerrUser
|
||||||
import com.github.damontecres.wholphin.data.model.hasPermission
|
import com.github.damontecres.wholphin.data.model.hasPermission
|
||||||
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import dagger.hilt.android.qualifiers.ActivityContext
|
import dagger.hilt.android.qualifiers.ActivityContext
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
@ -57,6 +59,7 @@ class SeerrServerRepository
|
||||||
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.server }
|
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.server }
|
||||||
val currentUser: Flow<SeerrUser?> =
|
val currentUser: Flow<SeerrUser?> =
|
||||||
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.user }
|
connection.map { (it as? SeerrConnectionStatus.Success)?.current?.user }
|
||||||
|
val currentUserId: Flow<Int?> = current.map { it?.config?.id }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether Seerr integration is currently active of not
|
* Whether Seerr integration is currently active of not
|
||||||
|
|
@ -70,10 +73,11 @@ class SeerrServerRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
fun error(
|
fun error(
|
||||||
serverUrl: String,
|
server: SeerrServer,
|
||||||
|
user: SeerrUser,
|
||||||
exception: Exception,
|
exception: Exception,
|
||||||
) {
|
) {
|
||||||
_connection.update { SeerrConnectionStatus.Error(serverUrl, exception) }
|
_connection.update { SeerrConnectionStatus.Error(server, user, exception) }
|
||||||
seerrApi.update("", null)
|
seerrApi.update("", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +165,7 @@ class SeerrServerRepository
|
||||||
val apiKey = passwordOrApiKey.takeIf { authMethod == SeerrAuthMethod.API_KEY }
|
val apiKey = passwordOrApiKey.takeIf { authMethod == SeerrAuthMethod.API_KEY }
|
||||||
val api =
|
val api =
|
||||||
SeerrApiClient(
|
SeerrApiClient(
|
||||||
url,
|
createSeerrApiUrl(url),
|
||||||
apiKey,
|
apiKey,
|
||||||
okHttpClient
|
okHttpClient
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
|
|
@ -174,8 +178,13 @@ class SeerrServerRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeServerForCurrentUser(): Boolean {
|
suspend fun removeServerForCurrentUser(): Boolean {
|
||||||
val current = current.firstOrNull() ?: return false
|
val user =
|
||||||
val rows = seerrServerDao.deleteUser(current.server.id, current.user.jellyfinUserRowId)
|
when (val conn = connection.first()) {
|
||||||
|
SeerrConnectionStatus.NotConfigured -> return false
|
||||||
|
is SeerrConnectionStatus.Error -> conn.user
|
||||||
|
is SeerrConnectionStatus.Success -> conn.current.user
|
||||||
|
}
|
||||||
|
val rows = seerrServerDao.deleteUser(user)
|
||||||
clear()
|
clear()
|
||||||
return rows > 0
|
return rows > 0
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +199,8 @@ sealed interface SeerrConnectionStatus {
|
||||||
data object NotConfigured : SeerrConnectionStatus
|
data object NotConfigured : SeerrConnectionStatus
|
||||||
|
|
||||||
data class Error(
|
data class Error(
|
||||||
val serverUrl: String,
|
val server: SeerrServer,
|
||||||
|
val user: SeerrUser,
|
||||||
val ex: Exception,
|
val ex: Exception,
|
||||||
) : SeerrConnectionStatus
|
) : SeerrConnectionStatus
|
||||||
|
|
||||||
|
|
@ -316,10 +326,31 @@ class UserSwitchListener
|
||||||
"Error logging into %s",
|
"Error logging into %s",
|
||||||
server.url,
|
server.url,
|
||||||
)
|
)
|
||||||
seerrServerRepository.error(server.url, ex)
|
seerrServerRepository.error(server, seerrUser, ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CurrentSeerr?.imageUrlBuilder(
|
||||||
|
imageType: ImageType,
|
||||||
|
path: String?,
|
||||||
|
): String? {
|
||||||
|
if (this == null) return null
|
||||||
|
val cacheImages = serverConfig.cacheImages == true
|
||||||
|
val base =
|
||||||
|
if (cacheImages) {
|
||||||
|
server.url.removeSuffix("/") + "/imageproxy/tmdb"
|
||||||
|
} else {
|
||||||
|
"https://image.tmdb.org"
|
||||||
|
}
|
||||||
|
val prefix =
|
||||||
|
when (imageType) {
|
||||||
|
ImageType.PRIMARY -> "/t/p/w500"
|
||||||
|
ImageType.BACKDROP -> "/t/p/w1920_and_h1080_multi_faces"
|
||||||
|
else -> throw IllegalArgumentException("Image type not supported: $imageType")
|
||||||
|
}
|
||||||
|
return "${base}${prefix}$path"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,25 @@
|
||||||
package com.github.damontecres.wholphin.services
|
package com.github.damontecres.wholphin.services
|
||||||
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.SeerrApiClient
|
import com.github.damontecres.wholphin.api.seerr.SeerrApiClient
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.CreditCast
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.CreditCrew
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.MediaInfo
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResultsInner
|
import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResultsInner
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.TvResult
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||||
|
import com.github.damontecres.wholphin.data.model.SeerrAvailability
|
||||||
|
import com.github.damontecres.wholphin.data.model.SeerrItemType
|
||||||
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.wholphin.ui.toLocalDate
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
@ -22,6 +36,7 @@ class SeerrService
|
||||||
constructor(
|
constructor(
|
||||||
private val seerApi: SeerrApi,
|
private val seerApi: SeerrApi,
|
||||||
private val seerrServerRepository: SeerrServerRepository,
|
private val seerrServerRepository: SeerrServerRepository,
|
||||||
|
private val imageUrlService: ImageUrlService,
|
||||||
) {
|
) {
|
||||||
val api: SeerrApiClient get() = seerApi.api
|
val api: SeerrApiClient get() = seerApi.api
|
||||||
|
|
||||||
|
|
@ -40,35 +55,35 @@ class SeerrService
|
||||||
api.searchApi
|
api.searchApi
|
||||||
.discoverTvGet(page = page)
|
.discoverTvGet(page = page)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
suspend fun discoverMovies(page: Int = 1): List<DiscoverItem> =
|
suspend fun discoverMovies(page: Int = 1): List<DiscoverItem> =
|
||||||
api.searchApi
|
api.searchApi
|
||||||
.discoverMoviesGet(page = page)
|
.discoverMoviesGet(page = page)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
suspend fun trending(page: Int = 1): List<DiscoverItem> =
|
suspend fun trending(page: Int = 1): List<DiscoverItem> =
|
||||||
api.searchApi
|
api.searchApi
|
||||||
.discoverTrendingGet(page = page)
|
.discoverTrendingGet(page = page)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
suspend fun upcomingMovies(page: Int = 1): List<DiscoverItem> =
|
suspend fun upcomingMovies(page: Int = 1): List<DiscoverItem> =
|
||||||
api.searchApi
|
api.searchApi
|
||||||
.discoverMoviesUpcomingGet(page = page)
|
.discoverMoviesUpcomingGet(page = page)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
suspend fun upcomingTv(page: Int = 1): List<DiscoverItem> =
|
suspend fun upcomingTv(page: Int = 1): List<DiscoverItem> =
|
||||||
api.searchApi
|
api.searchApi
|
||||||
.discoverTvUpcomingGet(page = page)
|
.discoverTvUpcomingGet(page = page)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -89,14 +104,14 @@ class SeerrService
|
||||||
api.moviesApi
|
api.moviesApi
|
||||||
.movieMovieIdSimilarGet(movieId = it)
|
.movieMovieIdSimilarGet(movieId = it)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseItemKind.SERIES, BaseItemKind.SEASON, BaseItemKind.EPISODE -> {
|
BaseItemKind.SERIES, BaseItemKind.SEASON, BaseItemKind.EPISODE -> {
|
||||||
api.tvApi
|
api.tvApi
|
||||||
.tvTvIdSimilarGet(tvId = it)
|
.tvTvIdSimilarGet(tvId = it)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseItemKind.PERSON -> {
|
BaseItemKind.PERSON -> {
|
||||||
|
|
@ -106,12 +121,12 @@ class SeerrService
|
||||||
val cast =
|
val cast =
|
||||||
credits.cast
|
credits.cast
|
||||||
?.take(25)
|
?.take(25)
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
val crew =
|
val crew =
|
||||||
credits.crew
|
credits.crew
|
||||||
?.take(25)
|
?.take(25)
|
||||||
?.map(::DiscoverItem)
|
?.map { createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
cast + crew
|
cast + crew
|
||||||
}
|
}
|
||||||
|
|
@ -125,4 +140,186 @@ class SeerrService
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getTvSeries(item: BaseItem): TvDetails? =
|
||||||
|
if (active.first()) {
|
||||||
|
item.data.providerIds
|
||||||
|
?.get("Tmdb")
|
||||||
|
?.toIntOrNull()
|
||||||
|
?.let { tvId ->
|
||||||
|
api.tvApi.tvTvIdGet(tvId = tvId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun createImageUrl(
|
||||||
|
imageType: ImageType,
|
||||||
|
path: String?,
|
||||||
|
mediaInfo: MediaInfo?,
|
||||||
|
): String? {
|
||||||
|
if (mediaInfo != null) {
|
||||||
|
val itemId =
|
||||||
|
if (mediaInfo.jellyfinMediaId.isNotNullOrBlank()) {
|
||||||
|
mediaInfo.jellyfinMediaId.toUUIDOrNull()
|
||||||
|
} else if (mediaInfo.jellyfinMediaId4k.isNotNullOrBlank()) {
|
||||||
|
mediaInfo.jellyfinMediaId4k.toUUIDOrNull()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (itemId != null) {
|
||||||
|
return imageUrlService.getItemImageUrl(
|
||||||
|
itemId = itemId,
|
||||||
|
imageType = imageType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val current = seerrServerRepository.current.firstOrNull() ?: return null
|
||||||
|
val cacheImages = current.serverConfig.cacheImages == true
|
||||||
|
val base =
|
||||||
|
if (cacheImages) {
|
||||||
|
current.server.url.removeSuffix("/") + "/imageproxy/tmdb"
|
||||||
|
} else {
|
||||||
|
"https://image.tmdb.org"
|
||||||
|
}
|
||||||
|
val prefix =
|
||||||
|
when (imageType) {
|
||||||
|
ImageType.PRIMARY -> "/t/p/w500"
|
||||||
|
ImageType.BACKDROP -> "/t/p/w1920_and_h1080_multi_faces"
|
||||||
|
else -> throw IllegalArgumentException("Image type not supported: $imageType")
|
||||||
|
}
|
||||||
|
return "${base}${prefix}$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(movie: MovieResult): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = movie.id,
|
||||||
|
type = SeerrItemType.MOVIE,
|
||||||
|
title = movie.title,
|
||||||
|
subtitle = null,
|
||||||
|
overview = movie.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(movie.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(movie.releaseDate),
|
||||||
|
posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo),
|
||||||
|
backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo),
|
||||||
|
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(movie: MovieDetails): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = movie.id ?: -1,
|
||||||
|
type = SeerrItemType.MOVIE,
|
||||||
|
title = movie.title,
|
||||||
|
subtitle = null,
|
||||||
|
overview = movie.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(movie.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(movie.releaseDate),
|
||||||
|
posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo),
|
||||||
|
backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo),
|
||||||
|
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(tv: TvResult): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = tv.id!!,
|
||||||
|
type = SeerrItemType.TV,
|
||||||
|
title = tv.name,
|
||||||
|
subtitle = null,
|
||||||
|
overview = tv.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(tv.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(tv.firstAirDate),
|
||||||
|
posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo),
|
||||||
|
backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo),
|
||||||
|
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(tv: TvDetails): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = tv.id!!,
|
||||||
|
type = SeerrItemType.TV,
|
||||||
|
title = tv.name,
|
||||||
|
subtitle = null,
|
||||||
|
overview = tv.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(tv.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(tv.firstAirDate),
|
||||||
|
posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo),
|
||||||
|
backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo),
|
||||||
|
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(search: SeerrSearchResult): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = search.id,
|
||||||
|
type = SeerrItemType.fromString(search.mediaType),
|
||||||
|
title = search.title ?: search.name,
|
||||||
|
subtitle = null,
|
||||||
|
overview = search.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(search.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate),
|
||||||
|
posterUrl = createImageUrl(ImageType.PRIMARY, search.posterPath, search.mediaInfo),
|
||||||
|
backDropUrl = createImageUrl(ImageType.BACKDROP, search.backdropPath, search.mediaInfo),
|
||||||
|
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(credit: CreditCast): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = credit.id!!,
|
||||||
|
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
||||||
|
title = credit.name ?: credit.title,
|
||||||
|
subtitle = credit.character,
|
||||||
|
overview = credit.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(credit.firstAirDate),
|
||||||
|
posterUrl =
|
||||||
|
createImageUrl(
|
||||||
|
ImageType.PRIMARY,
|
||||||
|
credit.posterPath ?: credit.profilePath,
|
||||||
|
credit.mediaInfo,
|
||||||
|
),
|
||||||
|
backDropUrl =
|
||||||
|
createImageUrl(
|
||||||
|
ImageType.BACKDROP,
|
||||||
|
credit.backdropPath,
|
||||||
|
credit.mediaInfo,
|
||||||
|
),
|
||||||
|
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun createDiscoverItem(credit: CreditCrew): DiscoverItem =
|
||||||
|
DiscoverItem(
|
||||||
|
id = credit.id!!,
|
||||||
|
type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
|
||||||
|
title = credit.name ?: credit.title,
|
||||||
|
subtitle = credit.job,
|
||||||
|
overview = credit.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = toLocalDate(credit.firstAirDate),
|
||||||
|
posterUrl =
|
||||||
|
createImageUrl(
|
||||||
|
ImageType.PRIMARY,
|
||||||
|
credit.posterPath ?: credit.profilePath,
|
||||||
|
credit.mediaInfo,
|
||||||
|
),
|
||||||
|
backDropUrl =
|
||||||
|
createImageUrl(
|
||||||
|
ImageType.BACKDROP,
|
||||||
|
credit.backdropPath,
|
||||||
|
credit.mediaInfo,
|
||||||
|
),
|
||||||
|
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.github.damontecres.wholphin.BuildConfig
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.services.UpdateChecker.Companion.ASSET_NAME
|
||||||
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
|
|
@ -51,9 +53,8 @@ class UpdateChecker
|
||||||
@param:StandardOkHttpClient private val okHttpClient: OkHttpClient,
|
@param:StandardOkHttpClient private val okHttpClient: OkHttpClient,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// TODO apk names
|
const val ASSET_NAME = "Wholphin"
|
||||||
private const val ASSET_NAME = "Wholphin"
|
const val APK_NAME = "$ASSET_NAME.apk"
|
||||||
private const val APK_NAME = "$ASSET_NAME.apk"
|
|
||||||
|
|
||||||
private const val APK_MIME_TYPE = "application/vnd.android.package-archive"
|
private const val APK_MIME_TYPE = "application/vnd.android.package-archive"
|
||||||
|
|
||||||
|
|
@ -118,11 +119,6 @@ class UpdateChecker
|
||||||
|
|
||||||
suspend fun getLatestRelease(updateUrl: String): Release? {
|
suspend fun getLatestRelease(updateUrl: String): Release? {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val preferRelease =
|
|
||||||
PreferenceManager
|
|
||||||
.getDefaultSharedPreferences(context)
|
|
||||||
.getBoolean("updatePreferRelease", true)
|
|
||||||
|
|
||||||
val request =
|
val request =
|
||||||
Request
|
Request
|
||||||
.Builder()
|
.Builder()
|
||||||
|
|
@ -140,7 +136,7 @@ class UpdateChecker
|
||||||
val downloadUrl =
|
val downloadUrl =
|
||||||
result.jsonObject["assets"]
|
result.jsonObject["assets"]
|
||||||
?.jsonArray
|
?.jsonArray
|
||||||
?.let { assets -> getDownloadUrl(assets, preferRelease) }
|
?.let { assets -> getDownloadUrl(assets, BuildConfig.DEBUG) }
|
||||||
Timber.v("version=$version, downloadUrl=$downloadUrl")
|
Timber.v("version=$version, downloadUrl=$downloadUrl")
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
val notes =
|
val notes =
|
||||||
|
|
@ -165,35 +161,6 @@ class UpdateChecker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDownloadUrl(
|
|
||||||
assets: JsonArray,
|
|
||||||
preferRelease: Boolean,
|
|
||||||
): String? {
|
|
||||||
val abiSuffix = Build.SUPPORTED_ABIS.firstOrNull().let { if (it != null) "-$it" else "" }
|
|
||||||
val releaseSuffix = if (preferRelease) "-release" else "-debug"
|
|
||||||
val preferredNames =
|
|
||||||
listOf(
|
|
||||||
"$ASSET_NAME${releaseSuffix}$abiSuffix.apk",
|
|
||||||
"$ASSET_NAME$releaseSuffix.apk",
|
|
||||||
"$ASSET_NAME.apk",
|
|
||||||
)
|
|
||||||
var preferredAsset: JsonObject? = null
|
|
||||||
outer@ for (name in preferredNames) {
|
|
||||||
for (asset in assets) {
|
|
||||||
val assetName =
|
|
||||||
asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull
|
|
||||||
if (name == assetName) {
|
|
||||||
preferredAsset = asset.jsonObject
|
|
||||||
break@outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return preferredAsset
|
|
||||||
?.get("browser_download_url")
|
|
||||||
?.jsonPrimitive
|
|
||||||
?.contentOrNull
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun installRelease(
|
suspend fun installRelease(
|
||||||
release: Release,
|
release: Release,
|
||||||
callback: DownloadCallback,
|
callback: DownloadCallback,
|
||||||
|
|
@ -387,3 +354,33 @@ suspend fun copyTo(
|
||||||
}
|
}
|
||||||
return bytesCopied
|
return bytesCopied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDownloadUrl(
|
||||||
|
assets: JsonArray,
|
||||||
|
debug: Boolean,
|
||||||
|
supportedAbis: List<String> = Build.SUPPORTED_ABIS.toList(),
|
||||||
|
): String? {
|
||||||
|
val abiSuffix = supportedAbis.firstOrNull().let { if (it != null) "-$it" else "" }
|
||||||
|
val releaseSuffix = if (debug) "-debug" else "-release"
|
||||||
|
val preferredNames =
|
||||||
|
buildList {
|
||||||
|
add("$ASSET_NAME${releaseSuffix}$abiSuffix.apk")
|
||||||
|
add("$ASSET_NAME$releaseSuffix.apk")
|
||||||
|
if (!debug) add("$ASSET_NAME.apk")
|
||||||
|
}
|
||||||
|
var preferredAsset: JsonObject? = null
|
||||||
|
outer@ for (name in preferredNames) {
|
||||||
|
for (asset in assets) {
|
||||||
|
val assetName =
|
||||||
|
asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull
|
||||||
|
if (name == assetName) {
|
||||||
|
preferredAsset = asset.jsonObject
|
||||||
|
break@outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return preferredAsset
|
||||||
|
?.get("browser_download_url")
|
||||||
|
?.jsonPrimitive
|
||||||
|
?.contentOrNull
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ object AppModule {
|
||||||
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
||||||
scope.launch(ExceptionHandler()) {
|
scope.launch(ExceptionHandler()) {
|
||||||
appPreference.updateData {
|
appPreference.updateData {
|
||||||
preferences.appPreferences.updateInterfacePreferences {
|
it.updateInterfacePreferences {
|
||||||
putRememberedTabs(key(itemId), tabIndex)
|
putRememberedTabs(key(itemId), tabIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ fun AppScreensaverContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
var logoError by remember(currentItem) { mutableStateOf(false) }
|
var logoError by remember(currentItem) { mutableStateOf(false) }
|
||||||
val alignment = listOf(Alignment.BottomStart, Alignment.BottomEnd).random()
|
|
||||||
if (!logoError) {
|
if (!logoError) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model =
|
model =
|
||||||
|
|
@ -150,7 +149,7 @@ fun AppScreensaverContent(
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.align(alignment)
|
.align(Alignment.BottomStart)
|
||||||
.size(width = 240.dp, height = 120.dp)
|
.size(width = 240.dp, height = 120.dp)
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
)
|
)
|
||||||
|
|
@ -158,7 +157,7 @@ fun AppScreensaverContent(
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.align(alignment)
|
.align(Alignment.BottomStart)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.fillMaxWidth(.5f)
|
.fillMaxWidth(.5f)
|
||||||
.fillMaxHeight(.3f),
|
.fillMaxHeight(.3f),
|
||||||
|
|
@ -169,7 +168,7 @@ fun AppScreensaverContent(
|
||||||
style = MaterialTheme.typography.displaySmall,
|
style = MaterialTheme.typography.displaySmall,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.align(alignment),
|
modifier = Modifier.align(Alignment.BottomStart),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -771,6 +771,9 @@ fun CollectionFolderGrid(
|
||||||
onClickDelete = {
|
onClickDelete = {
|
||||||
showDeleteDialog = PositionItem(position, item)
|
showDeleteDialog = PositionItem(position, item)
|
||||||
},
|
},
|
||||||
|
onClickGoTo = {
|
||||||
|
onClickItem.invoke(position, it)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onDismissRequest = { moreDialog.makeAbsent() },
|
onDismissRequest = { moreDialog.makeAbsent() },
|
||||||
|
|
|
||||||
|
|
@ -632,6 +632,7 @@ fun chooseStream(
|
||||||
streams: List<MediaStream>,
|
streams: List<MediaStream>,
|
||||||
currentIndex: Int?,
|
currentIndex: Int?,
|
||||||
type: MediaStreamType,
|
type: MediaStreamType,
|
||||||
|
preferredSubtitleLanguage: String?,
|
||||||
onClick: (Int) -> Unit,
|
onClick: (Int) -> Unit,
|
||||||
): DialogParams =
|
): DialogParams =
|
||||||
DialogParams(
|
DialogParams(
|
||||||
|
|
@ -669,7 +670,15 @@ fun chooseStream(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addAll(
|
addAll(
|
||||||
streams.filter { it.type == type }.mapIndexed { index, stream ->
|
streams
|
||||||
|
.filter { it.type == type }
|
||||||
|
.let {
|
||||||
|
if (type == MediaStreamType.SUBTITLE && preferredSubtitleLanguage.isNotNullOrBlank()) {
|
||||||
|
it.sortedByDescending { it.language != null && it.language == preferredSubtitleLanguage }
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}.mapIndexed { index, stream ->
|
||||||
val simpleStream = SimpleMediaStream.from(context, stream, true)
|
val simpleStream = SimpleMediaStream.from(context, stream, true)
|
||||||
DialogItem(
|
DialogItem(
|
||||||
selected = currentIndex == stream.index,
|
selected = currentIndex == stream.index,
|
||||||
|
|
@ -677,7 +686,9 @@ fun chooseStream(
|
||||||
SelectedLeadingContent(currentIndex == stream.index)
|
SelectedLeadingContent(currentIndex == stream.index)
|
||||||
},
|
},
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(text = simpleStream.streamTitle ?: simpleStream.displayTitle)
|
Text(
|
||||||
|
text = simpleStream.streamTitle ?: simpleStream.displayTitle,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
if (simpleStream.streamTitle != null) Text(text = simpleStream.displayTitle)
|
if (simpleStream.streamTitle != null) Text(text = simpleStream.displayTitle)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.widget.Toast
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.services.PlaylistCreator
|
import com.github.damontecres.wholphin.services.PlaylistCreator
|
||||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -14,6 +15,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.jellyfin.sdk.model.api.MediaType
|
import org.jellyfin.sdk.model.api.MediaType
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -43,7 +45,13 @@ class AddPlaylistViewModel
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||||
|
try {
|
||||||
playlistCreator.addToServerPlaylist(playlistId, itemId)
|
playlistCreator.addToServerPlaylist(playlistId, itemId)
|
||||||
|
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.e(ex, "Error adding %s to playlist %s", itemId, playlistId)
|
||||||
|
showToast(context, "Error: ${ex.localizedMessage}", Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +63,8 @@ class AddPlaylistViewModel
|
||||||
val playlistId = playlistCreator.createServerPlaylist(playlistName, listOf(itemId))
|
val playlistId = playlistCreator.createServerPlaylist(playlistName, listOf(itemId))
|
||||||
if (playlistId == null) {
|
if (playlistId == null) {
|
||||||
showToast(context, "Error creating playlist", Toast.LENGTH_LONG)
|
showToast(context, "Error creating playlist", Toast.LENGTH_LONG)
|
||||||
|
} else {
|
||||||
|
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ fun CollectionFolderPhotoAlbum(
|
||||||
index = index,
|
index = index,
|
||||||
filter = CollectionFolderFilter(filter = viewModel.filter.value ?: GetItemsFilter()),
|
filter = CollectionFolderFilter(filter = viewModel.filter.value ?: GetItemsFilter()),
|
||||||
sortAndDirection = viewModel.sortAndDirection.value ?: SortAndDirection.DEFAULT,
|
sortAndDirection = viewModel.sortAndDirection.value ?: SortAndDirection.DEFAULT,
|
||||||
recursive = true,
|
recursive = recursive,
|
||||||
startSlideshow = false,
|
startSlideshow = false,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ data class MoreDialogActions(
|
||||||
val onClickAddPlaylist: (UUID) -> Unit,
|
val onClickAddPlaylist: (UUID) -> Unit,
|
||||||
val onSendMediaInfo: (UUID) -> Unit,
|
val onSendMediaInfo: (UUID) -> Unit,
|
||||||
val onClickDelete: (BaseItem) -> Unit,
|
val onClickDelete: (BaseItem) -> Unit,
|
||||||
|
val onClickGoTo: (BaseItem) -> Unit = { navigateTo(it.destination()) },
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ClearChosenStreams {
|
enum class ClearChosenStreams {
|
||||||
|
|
@ -246,7 +247,7 @@ fun buildMoreDialogItemsForHome(
|
||||||
context.getString(R.string.go_to),
|
context.getString(R.string.go_to),
|
||||||
Icons.Default.ArrowForward,
|
Icons.Default.ArrowForward,
|
||||||
) {
|
) {
|
||||||
actions.navigateTo(item.destination())
|
actions.onClickGoTo(item)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (item.type in supportedPlayableTypes) {
|
if (item.type in supportedPlayableTypes) {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ class DiscoverMovieViewModel
|
||||||
) {
|
) {
|
||||||
Timber.v("Init for movie %s", item.id)
|
Timber.v("Init for movie %s", item.id)
|
||||||
val movie = fetchAndSetItem().await()
|
val movie = fetchAndSetItem().await()
|
||||||
val discoveredItem = DiscoverItem(movie)
|
val discoveredItem = seerrService.createDiscoverItem(movie)
|
||||||
backdropService.submit(discoveredItem)
|
backdropService.submit(discoveredItem)
|
||||||
|
|
||||||
updateCanCancel()
|
updateCanCancel()
|
||||||
|
|
@ -121,7 +121,7 @@ class DiscoverMovieViewModel
|
||||||
seerrService.api.moviesApi
|
seerrService.api.moviesApi
|
||||||
.movieMovieIdSimilarGet(movieId = item.id, page = 1)
|
.movieMovieIdSimilarGet(movieId = item.id, page = 1)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
similar.setValueOnMain(result)
|
similar.setValueOnMain(result)
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +130,7 @@ class DiscoverMovieViewModel
|
||||||
seerrService.api.moviesApi
|
seerrService.api.moviesApi
|
||||||
.movieMovieIdRecommendationsGet(movieId = item.id, page = 1)
|
.movieMovieIdRecommendationsGet(movieId = item.id, page = 1)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
recommended.setValueOnMain(result)
|
recommended.setValueOnMain(result)
|
||||||
}
|
}
|
||||||
|
|
@ -138,11 +138,11 @@ class DiscoverMovieViewModel
|
||||||
val people =
|
val people =
|
||||||
movie.credits
|
movie.credits
|
||||||
?.cast
|
?.cast
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty() +
|
.orEmpty() +
|
||||||
movie.credits
|
movie.credits
|
||||||
?.crew
|
?.crew
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
this@DiscoverMovieViewModel.people.setValueOnMain(people)
|
this@DiscoverMovieViewModel.people.setValueOnMain(people)
|
||||||
val trailers =
|
val trailers =
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,11 @@ class DiscoverPersonViewModel
|
||||||
.let { credits ->
|
.let { credits ->
|
||||||
val cast =
|
val cast =
|
||||||
credits.cast
|
credits.cast
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
val crew =
|
val crew =
|
||||||
credits.crew
|
credits.crew
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
cast + crew
|
cast + crew
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.Season
|
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||||
|
|
@ -99,6 +98,7 @@ fun DiscoverSeriesDetails(
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||||
|
var showRequestSeasonDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val requestStr = stringResource(R.string.request)
|
val requestStr = stringResource(R.string.request)
|
||||||
val request4kStr = stringResource(R.string.request_4k)
|
val request4kStr = stringResource(R.string.request_4k)
|
||||||
|
|
@ -159,26 +159,7 @@ fun DiscoverSeriesDetails(
|
||||||
trailers = trailers,
|
trailers = trailers,
|
||||||
requestOnClick = {
|
requestOnClick = {
|
||||||
item.id?.let { id ->
|
item.id?.let { id ->
|
||||||
if (request4kEnabled) {
|
showRequestSeasonDialog = true
|
||||||
moreDialog =
|
|
||||||
DialogParams(
|
|
||||||
fromLongClick = false,
|
|
||||||
title = item.name ?: "",
|
|
||||||
items =
|
|
||||||
listOf(
|
|
||||||
DialogItem(
|
|
||||||
text = requestStr,
|
|
||||||
onClick = { viewModel.request(id, false) },
|
|
||||||
),
|
|
||||||
DialogItem(
|
|
||||||
text = request4kStr,
|
|
||||||
onClick = { viewModel.request(id, true) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
viewModel.request(id, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancelOnClick = {
|
cancelOnClick = {
|
||||||
|
|
@ -218,6 +199,18 @@ fun DiscoverSeriesDetails(
|
||||||
waitToLoad = params.fromLongClick,
|
waitToLoad = params.fromLongClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (showRequestSeasonDialog) {
|
||||||
|
RequestSeasonsDialog(
|
||||||
|
title = item?.name ?: "",
|
||||||
|
seasons = seasons,
|
||||||
|
request4kEnabled = request4kEnabled,
|
||||||
|
onSubmit = { seasons, is4k ->
|
||||||
|
item?.id?.let { viewModel.request(it, seasons, is4k) }
|
||||||
|
showRequestSeasonDialog = false
|
||||||
|
},
|
||||||
|
onDismissRequest = { showRequestSeasonDialog = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val HEADER_ROW = 0
|
private const val HEADER_ROW = 0
|
||||||
|
|
@ -234,7 +227,7 @@ fun DiscoverSeriesDetailsContent(
|
||||||
series: TvDetails,
|
series: TvDetails,
|
||||||
rating: DiscoverRating?,
|
rating: DiscoverRating?,
|
||||||
canCancel: Boolean,
|
canCancel: Boolean,
|
||||||
seasons: List<Season>,
|
seasons: List<RequestSeason>,
|
||||||
similar: List<DiscoverItem>,
|
similar: List<DiscoverItem>,
|
||||||
recommended: List<DiscoverItem>,
|
recommended: List<DiscoverItem>,
|
||||||
trailers: List<Trailer>,
|
trailers: List<Trailer>,
|
||||||
|
|
@ -299,6 +292,7 @@ fun DiscoverSeriesDetailsContent(
|
||||||
SeerrAvailability.from(series.mediaInfo?.status)
|
SeerrAvailability.from(series.mediaInfo?.status)
|
||||||
?: SeerrAvailability.UNKNOWN,
|
?: SeerrAvailability.UNKNOWN,
|
||||||
requestOnClick = requestOnClick,
|
requestOnClick = requestOnClick,
|
||||||
|
pendingOnClick = requestOnClick,
|
||||||
cancelOnClick = cancelOnClick,
|
cancelOnClick = cancelOnClick,
|
||||||
moreOnClick = moreOnClick,
|
moreOnClick = moreOnClick,
|
||||||
goToOnClick = goToOnClick,
|
goToOnClick = goToOnClick,
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
|
import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
|
import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.Season
|
import com.github.damontecres.wholphin.api.seerr.model.RequestRequestIdPutRequest
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||||
import com.github.damontecres.wholphin.data.model.DiscoverRating
|
import com.github.damontecres.wholphin.data.model.DiscoverRating
|
||||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||||
|
import com.github.damontecres.wholphin.data.model.SeerrAvailability
|
||||||
import com.github.damontecres.wholphin.data.model.Trailer
|
import com.github.damontecres.wholphin.data.model.Trailer
|
||||||
import com.github.damontecres.wholphin.services.BackdropService
|
import com.github.damontecres.wholphin.services.BackdropService
|
||||||
import com.github.damontecres.wholphin.services.NavigationManager
|
import com.github.damontecres.wholphin.services.NavigationManager
|
||||||
|
|
@ -33,6 +34,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
@ -63,7 +65,7 @@ class DiscoverSeriesViewModel
|
||||||
val tvSeries = MutableLiveData<TvDetails?>(null)
|
val tvSeries = MutableLiveData<TvDetails?>(null)
|
||||||
val rating = MutableLiveData<DiscoverRating?>(null)
|
val rating = MutableLiveData<DiscoverRating?>(null)
|
||||||
|
|
||||||
val seasons = MutableLiveData<List<Season>>(listOf())
|
val seasons = MutableLiveData<List<RequestSeason>>(listOf())
|
||||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||||
val people = MutableLiveData<List<DiscoverItem>>(listOf())
|
val people = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||||
val similar = MutableLiveData<List<DiscoverItem>>()
|
val similar = MutableLiveData<List<DiscoverItem>>()
|
||||||
|
|
@ -100,9 +102,10 @@ class DiscoverSeriesViewModel
|
||||||
) {
|
) {
|
||||||
Timber.v("Init for tv %s", item.id)
|
Timber.v("Init for tv %s", item.id)
|
||||||
val tv = fetchAndSetItem().await()
|
val tv = fetchAndSetItem().await()
|
||||||
val discoveredItem = DiscoverItem(tv)
|
val discoveredItem = seerrService.createDiscoverItem(tv)
|
||||||
backdropService.submit(discoveredItem)
|
backdropService.submit(discoveredItem)
|
||||||
|
|
||||||
|
updateSeasonStatus()
|
||||||
updateCanCancel()
|
updateCanCancel()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
@ -118,7 +121,7 @@ class DiscoverSeriesViewModel
|
||||||
seerrService.api.tvApi
|
seerrService.api.tvApi
|
||||||
.tvTvIdSimilarGet(tvId = item.id, page = 1)
|
.tvTvIdSimilarGet(tvId = item.id, page = 1)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
similar.setValueOnMain(result)
|
similar.setValueOnMain(result)
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +130,7 @@ class DiscoverSeriesViewModel
|
||||||
seerrService.api.tvApi
|
seerrService.api.tvApi
|
||||||
.tvTvIdRecommendationsGet(tvId = item.id, page = 1)
|
.tvTvIdRecommendationsGet(tvId = item.id, page = 1)
|
||||||
.results
|
.results
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
recommended.setValueOnMain(result)
|
recommended.setValueOnMain(result)
|
||||||
}
|
}
|
||||||
|
|
@ -135,11 +138,11 @@ class DiscoverSeriesViewModel
|
||||||
val people =
|
val people =
|
||||||
tv.credits
|
tv.credits
|
||||||
?.cast
|
?.cast
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty() +
|
.orEmpty() +
|
||||||
tv.credits
|
tv.credits
|
||||||
?.crew
|
?.crew
|
||||||
?.map(::DiscoverItem)
|
?.map { seerrService.createDiscoverItem(it) }
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
this@DiscoverSeriesViewModel.people.setValueOnMain(people)
|
this@DiscoverSeriesViewModel.people.setValueOnMain(people)
|
||||||
|
|
||||||
|
|
@ -157,6 +160,43 @@ class DiscoverSeriesViewModel
|
||||||
navigationManager.navigateTo(destination)
|
navigationManager.navigateTo(destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun updateSeasonStatus() {
|
||||||
|
tvSeries.value?.let { tv ->
|
||||||
|
val seasonStatus = mutableMapOf<Int, SeerrAvailability>()
|
||||||
|
tv.seasons?.forEach {
|
||||||
|
it.seasonNumber?.let {
|
||||||
|
seasonStatus[it] = SeerrAvailability.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val tvStatus =
|
||||||
|
SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN
|
||||||
|
tv.mediaInfo
|
||||||
|
?.requests
|
||||||
|
?.forEach {
|
||||||
|
it.seasons?.mapNotNull { season ->
|
||||||
|
season.seasonNumber?.let {
|
||||||
|
val current = seasonStatus[season.seasonNumber]
|
||||||
|
val new =
|
||||||
|
SeerrAvailability
|
||||||
|
.from(season.status)
|
||||||
|
?.takeIf { it != SeerrAvailability.UNKNOWN } ?: tvStatus
|
||||||
|
if (current == null || new.status > current.status) {
|
||||||
|
seasonStatus[season.seasonNumber] = new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.v("seasonStatus=%s", seasonStatus)
|
||||||
|
val requestSeasons =
|
||||||
|
seasonStatus.mapNotNull { (seasonNumber, availability) ->
|
||||||
|
tv.seasons?.firstOrNull { it.seasonNumber == seasonNumber }?.let {
|
||||||
|
RequestSeason(it, availability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seasons.setValueOnMain(requestSeasons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateCanCancel() {
|
private suspend fun updateCanCancel() {
|
||||||
val user = userConfig.firstOrNull()
|
val user = userConfig.firstOrNull()
|
||||||
val canCancel = canUserCancelRequest(user, tvSeries.value?.mediaInfo?.requests)
|
val canCancel = canUserCancelRequest(user, tvSeries.value?.mediaInfo?.requests)
|
||||||
|
|
@ -165,22 +205,45 @@ class DiscoverSeriesViewModel
|
||||||
|
|
||||||
fun request(
|
fun request(
|
||||||
id: Int,
|
id: Int,
|
||||||
|
seasons: Set<Int>,
|
||||||
is4k: Boolean,
|
is4k: Boolean,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val request =
|
tvSeries.value?.let { tv ->
|
||||||
|
val currentRequest =
|
||||||
|
tv.mediaInfo?.requests?.firstOrNull {
|
||||||
|
it.requestedBy?.id ==
|
||||||
|
seerrServerRepository.currentUserId.first()
|
||||||
|
}
|
||||||
|
if (currentRequest != null) {
|
||||||
|
Timber.v("User has pending request, will update")
|
||||||
|
seerrService.api.requestApi.requestRequestIdPut(
|
||||||
|
requestId = currentRequest.id.toString(),
|
||||||
|
requestRequestIdPutRequest =
|
||||||
|
RequestRequestIdPutRequest(
|
||||||
|
is4k = is4k,
|
||||||
|
mediaType = RequestRequestIdPutRequest.MediaType.TV,
|
||||||
|
seasons = seasons.toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Timber.v("New request for %s seasons", seasons.size)
|
||||||
seerrService.api.requestApi.requestPost(
|
seerrService.api.requestApi.requestPost(
|
||||||
RequestPostRequest(
|
RequestPostRequest(
|
||||||
is4k = is4k,
|
is4k = is4k,
|
||||||
mediaId = id,
|
mediaId = id,
|
||||||
mediaType = RequestPostRequest.MediaType.TV,
|
mediaType = RequestPostRequest.MediaType.TV,
|
||||||
seasons = RequestPostRequest.Seasons.ALL, // TODO handle picking seasons
|
seasons = seasons.toList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fetchAndSetItem().await()
|
fetchAndSetItem().await()
|
||||||
|
updateSeasonStatus()
|
||||||
updateCanCancel()
|
updateCanCancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelRequest(id: Int) {
|
fun cancelRequest(id: Int) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ fun ExpandableDiscoverButtons(
|
||||||
trailerOnClick: (Trailer) -> Unit,
|
trailerOnClick: (Trailer) -> Unit,
|
||||||
buttonOnFocusChanged: (FocusState) -> Unit,
|
buttonOnFocusChanged: (FocusState) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
pendingOnClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val firstFocus = remember { FocusRequester() }
|
val firstFocus = remember { FocusRequester() }
|
||||||
LazyRow(
|
LazyRow(
|
||||||
|
|
@ -89,7 +90,7 @@ fun ExpandableDiscoverButtons(
|
||||||
SeerrAvailability.PENDING,
|
SeerrAvailability.PENDING,
|
||||||
SeerrAvailability.PROCESSING,
|
SeerrAvailability.PROCESSING,
|
||||||
-> {
|
-> {
|
||||||
// TODO?
|
pendingOnClick.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
SeerrAvailability.PARTIALLY_AVAILABLE,
|
SeerrAvailability.PARTIALLY_AVAILABLE,
|
||||||
|
|
@ -109,6 +110,21 @@ fun ExpandableDiscoverButtons(
|
||||||
.onFocusChanged(buttonOnFocusChanged),
|
.onFocusChanged(buttonOnFocusChanged),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (availability == SeerrAvailability.PARTIALLY_AVAILABLE) {
|
||||||
|
item("request_partial") {
|
||||||
|
ExpandableFaButton(
|
||||||
|
title = R.string.request,
|
||||||
|
iconStringRes = R.string.fa_download,
|
||||||
|
onClick = {
|
||||||
|
requestOnClick.invoke()
|
||||||
|
},
|
||||||
|
enabled = availability == SeerrAvailability.PARTIALLY_AVAILABLE,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.onFocusChanged(buttonOnFocusChanged),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (canCancel) {
|
if (canCancel) {
|
||||||
item("cancel") {
|
item("cancel") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.detail.discover
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateSetOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.tv.material3.Button
|
||||||
|
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||||
|
import androidx.tv.material3.ListItem
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Surface
|
||||||
|
import androidx.tv.material3.Switch
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import androidx.tv.material3.contentColorFor
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.Season
|
||||||
|
import com.github.damontecres.wholphin.data.model.SeerrAvailability
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.AvailableIndicator
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.PartiallyAvailableIndicator
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.PendingIndicator
|
||||||
|
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||||
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
|
|
||||||
|
data class RequestSeason(
|
||||||
|
val season: Season,
|
||||||
|
val availability: SeerrAvailability,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RequestSeasons(
|
||||||
|
title: String,
|
||||||
|
seasons: List<RequestSeason>,
|
||||||
|
onSubmit: (Set<Int>, Boolean) -> Unit,
|
||||||
|
request4kEnabled: Boolean,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
val allSeasonNumbers = remember(seasons) { seasons.mapNotNull { it.season.seasonNumber }.toSet() }
|
||||||
|
val selected =
|
||||||
|
remember {
|
||||||
|
mutableStateSetOf<Int>(
|
||||||
|
*seasons
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.availability > SeerrAvailability.UNKNOWN) {
|
||||||
|
it.season.seasonNumber
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toTypedArray(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var is4k by remember { mutableStateOf(false) }
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
val isSelected = selected.containsAll(allSeasonNumbers)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
ClickSwitch(
|
||||||
|
label = stringResource(R.string.select_all),
|
||||||
|
checked = isSelected,
|
||||||
|
onClick = {
|
||||||
|
if (isSelected) {
|
||||||
|
selected.removeAll(allSeasonNumbers)
|
||||||
|
} else {
|
||||||
|
selected.addAll(allSeasonNumbers)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = { onSubmit.invoke(selected, is4k) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.submit),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request4kEnabled) {
|
||||||
|
item {
|
||||||
|
ClickSwitch(
|
||||||
|
label = stringResource(R.string.request_4k),
|
||||||
|
checked = is4k,
|
||||||
|
onClick = { is4k = !is4k },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(seasons) { index, season ->
|
||||||
|
val seasonNumber = season.season.seasonNumber
|
||||||
|
val isSelected = seasonNumber in selected
|
||||||
|
SeasonListItem(
|
||||||
|
season = season,
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = {
|
||||||
|
if (isSelected) {
|
||||||
|
selected.remove(seasonNumber)
|
||||||
|
} else {
|
||||||
|
seasonNumber?.let { selected.add(it) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (seasons.size > 7) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = { onSubmit.invoke(selected, is4k) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.submit),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SeasonListItem(
|
||||||
|
season: RequestSeason,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
selected = false,
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
season.season.name
|
||||||
|
?: (stringResource(R.string.tv_season) + " ${season.season.seasonNumber}"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
season.season.episodeCount?.let {
|
||||||
|
Text(
|
||||||
|
// TODO should use plurals string
|
||||||
|
text = "${season.season.episodeCount} " + stringResource(R.string.episodes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
when (season.availability) {
|
||||||
|
SeerrAvailability.UNKNOWN -> {}
|
||||||
|
|
||||||
|
SeerrAvailability.DELETED -> {}
|
||||||
|
|
||||||
|
SeerrAvailability.PENDING,
|
||||||
|
SeerrAvailability.PROCESSING,
|
||||||
|
-> {
|
||||||
|
PendingIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
SeerrAvailability.PARTIALLY_AVAILABLE -> {
|
||||||
|
PartiallyAvailableIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
SeerrAvailability.AVAILABLE -> {
|
||||||
|
AvailableIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Row {
|
||||||
|
Switch(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = {
|
||||||
|
onClick.invoke()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ClickSurface(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable BoxScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
colors =
|
||||||
|
ClickableSurfaceDefaults.colors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
focusedContainerColor = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
focusedContentColor = contentColorFor(MaterialTheme.colorScheme.inverseSurface),
|
||||||
|
pressedContainerColor = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
pressedContentColor = contentColorFor(MaterialTheme.colorScheme.inverseSurface),
|
||||||
|
),
|
||||||
|
onClick = onClick,
|
||||||
|
content = content,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ClickSwitch(
|
||||||
|
label: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
ClickSurface(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.height(54.dp),
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = {},
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RequestSeasonsDialog(
|
||||||
|
title: String,
|
||||||
|
seasons: List<RequestSeason>,
|
||||||
|
request4kEnabled: Boolean,
|
||||||
|
onSubmit: (Set<Int>, Boolean) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
BasicDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
) {
|
||||||
|
RequestSeasons(
|
||||||
|
title = title,
|
||||||
|
seasons = seasons,
|
||||||
|
request4kEnabled = request4kEnabled,
|
||||||
|
onSubmit = onSubmit,
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(
|
||||||
|
device = "spec:parent=tv_1080p",
|
||||||
|
backgroundColor = 0xFF383535,
|
||||||
|
uiMode = UI_MODE_TYPE_TELEVISION,
|
||||||
|
heightDp = 800,
|
||||||
|
)
|
||||||
|
@Composable
|
||||||
|
fun RequestSeasonsPreview() {
|
||||||
|
val seasons =
|
||||||
|
List(10) {
|
||||||
|
RequestSeason(
|
||||||
|
season =
|
||||||
|
Season(
|
||||||
|
seasonNumber = it + 1,
|
||||||
|
episodeCount = 10 + it,
|
||||||
|
),
|
||||||
|
availability =
|
||||||
|
if (it < 3) {
|
||||||
|
SeerrAvailability.AVAILABLE
|
||||||
|
} else {
|
||||||
|
SeerrAvailability.UNKNOWN
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
WholphinTheme {
|
||||||
|
RequestSeasons(
|
||||||
|
title = "Series title",
|
||||||
|
seasons = seasons,
|
||||||
|
request4kEnabled = true,
|
||||||
|
onSubmit = { _, _ -> },
|
||||||
|
modifier = Modifier.width(400.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,13 @@ fun EpisodeDetails(
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
||||||
|
val preferredSubtitleLanguage =
|
||||||
|
viewModel.serverRepository.currentUserDto
|
||||||
|
.observeAsState()
|
||||||
|
.value
|
||||||
|
?.configuration
|
||||||
|
?.subtitleLanguagePreference
|
||||||
|
|
||||||
val moreActions =
|
val moreActions =
|
||||||
MoreDialogActions(
|
MoreDialogActions(
|
||||||
navigateTo = viewModel::navigateTo,
|
navigateTo = viewModel::navigateTo,
|
||||||
|
|
@ -200,6 +207,7 @@ fun EpisodeDetails(
|
||||||
type,
|
type,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,13 @@ fun MovieDetails(
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||||
|
|
||||||
|
val preferredSubtitleLanguage =
|
||||||
|
viewModel.serverRepository.currentUserDto
|
||||||
|
.observeAsState()
|
||||||
|
.value
|
||||||
|
?.configuration
|
||||||
|
?.subtitleLanguagePreference
|
||||||
|
|
||||||
val moreActions =
|
val moreActions =
|
||||||
MoreDialogActions(
|
MoreDialogActions(
|
||||||
navigateTo = viewModel::navigateTo,
|
navigateTo = viewModel::navigateTo,
|
||||||
|
|
@ -246,6 +253,7 @@ fun MovieDetails(
|
||||||
type,
|
type,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.series
|
package com.github.damontecres.wholphin.ui.detail.series
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -120,6 +123,7 @@ fun SeriesDetails(
|
||||||
val people by viewModel.people.observeAsState(listOf())
|
val people by viewModel.people.observeAsState(listOf())
|
||||||
val similar by viewModel.similar.observeAsState(listOf())
|
val similar by viewModel.similar.observeAsState(listOf())
|
||||||
val discovered by viewModel.discovered.collectAsState()
|
val discovered by viewModel.discovered.collectAsState()
|
||||||
|
val discoverSeries by viewModel.discoverSeries.collectAsState()
|
||||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||||
|
|
||||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||||
|
|
@ -230,6 +234,12 @@ fun SeriesDetails(
|
||||||
onClickExtra = { _, extra ->
|
onClickExtra = { _, extra ->
|
||||||
viewModel.navigateTo(extra.destination)
|
viewModel.navigateTo(extra.destination)
|
||||||
},
|
},
|
||||||
|
discoverSeries = discoverSeries,
|
||||||
|
onClickDiscoverSeries = {
|
||||||
|
discoverSeries?.let {
|
||||||
|
viewModel.navigateTo(Destination.DiscoveredItem(it))
|
||||||
|
}
|
||||||
|
},
|
||||||
discovered = discovered,
|
discovered = discovered,
|
||||||
onClickDiscover = { index, item ->
|
onClickDiscover = { index, item ->
|
||||||
viewModel.navigateTo(item.destination)
|
viewModel.navigateTo(item.destination)
|
||||||
|
|
@ -350,6 +360,8 @@ fun SeriesDetailsContent(
|
||||||
onClickExtra: (Int, ExtrasItem) -> Unit,
|
onClickExtra: (Int, ExtrasItem) -> Unit,
|
||||||
moreActions: MoreDialogActions,
|
moreActions: MoreDialogActions,
|
||||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||||
|
discoverSeries: DiscoverItem?,
|
||||||
|
onClickDiscoverSeries: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -487,6 +499,25 @@ fun SeriesDetailsContent(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = discoverSeries != null,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
ExpandableFaButton(
|
||||||
|
title = R.string.discover,
|
||||||
|
iconStringRes = R.string.fa_magnifying_glass_plus,
|
||||||
|
onClick = onClickDiscoverSeries,
|
||||||
|
modifier =
|
||||||
|
Modifier.onFocusChanged {
|
||||||
|
if (it.isFocused) {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
|
||||||
|
|
@ -125,20 +125,6 @@ fun SeriesOverview(
|
||||||
var rowFocused by rememberInt()
|
var rowFocused by rememberInt()
|
||||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(episodes) {
|
|
||||||
episodes?.let { episodes ->
|
|
||||||
if (episodes is EpisodeList.Success) {
|
|
||||||
if (episodes.episodes.isNotEmpty()) {
|
|
||||||
// TODO focus on first episode when changing seasons?
|
|
||||||
// firstItemFocusRequester.requestFocus()
|
|
||||||
episodes.episodes.getOrNull(position.episodeRowIndex)?.let {
|
|
||||||
viewModel.refreshEpisode(it.id, position.episodeRowIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(position, episodes) {
|
LaunchedEffect(position, episodes) {
|
||||||
val focusedEpisode =
|
val focusedEpisode =
|
||||||
(episodes as? EpisodeList.Success)
|
(episodes as? EpisodeList.Success)
|
||||||
|
|
@ -152,6 +138,13 @@ fun SeriesOverview(
|
||||||
}
|
}
|
||||||
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
||||||
|
|
||||||
|
val preferredSubtitleLanguage =
|
||||||
|
viewModel.serverRepository.currentUserDto
|
||||||
|
.observeAsState()
|
||||||
|
.value
|
||||||
|
?.configuration
|
||||||
|
?.subtitleLanguagePreference
|
||||||
|
|
||||||
when (val state = loading) {
|
when (val state = loading) {
|
||||||
is LoadingState.Error -> {
|
is LoadingState.Error -> {
|
||||||
ErrorMessage(state, modifier)
|
ErrorMessage(state, modifier)
|
||||||
|
|
@ -266,6 +259,7 @@ fun SeriesOverview(
|
||||||
type,
|
type,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
@ -123,6 +124,7 @@ class SeriesViewModel
|
||||||
|
|
||||||
val peopleInEpisode = MutableLiveData<PeopleInItem>(PeopleInItem())
|
val peopleInEpisode = MutableLiveData<PeopleInItem>(PeopleInItem())
|
||||||
val discovered = MutableStateFlow<List<DiscoverItem>>(listOf())
|
val discovered = MutableStateFlow<List<DiscoverItem>>(listOf())
|
||||||
|
val discoverSeries = MutableStateFlow<DiscoverItem?>(null)
|
||||||
|
|
||||||
val position = MutableStateFlow(SeriesOverviewPosition(0, 0))
|
val position = MutableStateFlow(SeriesOverviewPosition(0, 0))
|
||||||
|
|
||||||
|
|
@ -232,6 +234,24 @@ class SeriesViewModel
|
||||||
val results = seerrService.similar(item).orEmpty()
|
val results = seerrService.similar(item).orEmpty()
|
||||||
discovered.update { results }
|
discovered.update { results }
|
||||||
}
|
}
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
seerrService.active.collectLatest { active ->
|
||||||
|
val tv =
|
||||||
|
if (active) {
|
||||||
|
try {
|
||||||
|
seerrService
|
||||||
|
.getTvSeries(item)
|
||||||
|
?.let { seerrService.createDiscoverItem(it) }
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.e(ex)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
discoverSeries.update { tv }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mediaManagementService.deletedItemFlow
|
mediaManagementService.deletedItemFlow
|
||||||
.onEach { deletedItem ->
|
.onEach { deletedItem ->
|
||||||
|
|
@ -369,12 +389,6 @@ class SeriesViewModel
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@SeriesViewModel.episodes.value = episodes
|
this@SeriesViewModel.episodes.value = episodes
|
||||||
}
|
}
|
||||||
if (currentEpisodes == null || currentEpisodes.seasonId != seasonId) {
|
|
||||||
(episodes as? EpisodeList.Success)
|
|
||||||
?.let {
|
|
||||||
it.episodes.getOrNull(it.initialEpisodeIndex)
|
|
||||||
}?.let { lookupPeopleInEpisode(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,13 +85,13 @@ class SeerrRequestsViewModel
|
||||||
seerrService.api.moviesApi
|
seerrService.api.moviesApi
|
||||||
.movieMovieIdGet(
|
.movieMovieIdGet(
|
||||||
movieId = request.media.tmdbId,
|
movieId = request.media.tmdbId,
|
||||||
).let { DiscoverItem(it) }
|
).let { seerrService.createDiscoverItem(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
SeerrItemType.TV -> {
|
SeerrItemType.TV -> {
|
||||||
seerrService.api.tvApi
|
seerrService.api.tvApi
|
||||||
.tvTvIdGet(tvId = request.media.tmdbId)
|
.tvTvIdGet(tvId = request.media.tmdbId)
|
||||||
.let { DiscoverItem(it) }
|
.let { seerrService.createDiscoverItem(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
SeerrItemType.PERSON -> {
|
SeerrItemType.PERSON -> {
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ class SearchViewModel
|
||||||
val results =
|
val results =
|
||||||
seerrService
|
seerrService
|
||||||
.search(query)
|
.search(query)
|
||||||
.map { DiscoverItem(it) }
|
.map { seerrService.createDiscoverItem(it) }
|
||||||
.filter { it.type == SeerrItemType.MOVIE || it.type == SeerrItemType.TV }
|
.filter { it.type == SeerrItemType.MOVIE || it.type == SeerrItemType.TV }
|
||||||
seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results))
|
seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ fun DestinationContent(
|
||||||
CollectionFolderPhotoAlbum(
|
CollectionFolderPhotoAlbum(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
itemId = destination.itemId,
|
itemId = destination.itemId,
|
||||||
recursive = true,
|
recursive = false,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -387,10 +387,19 @@ fun CollectionFolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionType.HOMEVIDEOS,
|
CollectionType.HOMEVIDEOS,
|
||||||
|
CollectionType.PHOTOS,
|
||||||
|
-> {
|
||||||
|
CollectionFolderPhotoAlbum(
|
||||||
|
preferences = preferences,
|
||||||
|
itemId = destination.itemId,
|
||||||
|
recursive = recursiveOverride ?: false,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
CollectionType.MUSICVIDEOS,
|
CollectionType.MUSICVIDEOS,
|
||||||
CollectionType.MUSIC,
|
CollectionType.MUSIC,
|
||||||
CollectionType.BOOKS,
|
CollectionType.BOOKS,
|
||||||
CollectionType.PHOTOS,
|
|
||||||
-> {
|
-> {
|
||||||
CollectionFolderGeneric(
|
CollectionFolderGeneric(
|
||||||
preferences,
|
preferences,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.annotation.OptIn
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -24,7 +23,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -37,7 +35,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
|
@ -64,6 +61,7 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.components.Button
|
import com.github.damontecres.wholphin.ui.components.Button
|
||||||
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
||||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
import com.github.damontecres.wholphin.ui.components.TextButton
|
||||||
|
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||||
import com.github.damontecres.wholphin.ui.seekBack
|
import com.github.damontecres.wholphin.ui.seekBack
|
||||||
import com.github.damontecres.wholphin.ui.seekForward
|
import com.github.damontecres.wholphin.ui.seekForward
|
||||||
import com.github.damontecres.wholphin.ui.skipStringRes
|
import com.github.damontecres.wholphin.ui.skipStringRes
|
||||||
|
|
@ -470,6 +468,14 @@ fun <T> BottomDialog(
|
||||||
gravity: Int,
|
gravity: Int,
|
||||||
currentChoice: BottomDialogItem<T>? = null,
|
currentChoice: BottomDialogItem<T>? = null,
|
||||||
) {
|
) {
|
||||||
|
val focusRequesters = remember(choices.size) { List(choices.size) { FocusRequester() } }
|
||||||
|
if (currentChoice != null) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
choices.indexOfFirstOrNull { it == currentChoice }?.let {
|
||||||
|
focusRequesters.getOrNull(it)?.tryRequestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO enforcing a width ends up ignore the gravity
|
// TODO enforcing a width ends up ignore the gravity
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
@ -504,6 +510,7 @@ fun <T> BottomDialog(
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
ListItem(
|
ListItem(
|
||||||
selected = choice == currentChoice,
|
selected = choice == currentChoice,
|
||||||
|
enabled = choice.enabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onSelectChoice(index, choice)
|
onSelectChoice(index, choice)
|
||||||
|
|
@ -524,6 +531,7 @@ fun <T> BottomDialog(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[index]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -539,6 +547,7 @@ data class BottomDialogItem<T>(
|
||||||
val data: T,
|
val data: T,
|
||||||
val headline: String,
|
val headline: String,
|
||||||
val supporting: String?,
|
val supporting: String?,
|
||||||
|
val enabled: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
@PreviewTvSpec
|
@PreviewTvSpec
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,12 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -32,6 +35,8 @@ import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.TrackIndex
|
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||||
import com.github.damontecres.wholphin.ui.AppColors
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
||||||
|
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||||
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
enum class PlaybackDialogType {
|
enum class PlaybackDialogType {
|
||||||
|
|
@ -54,6 +59,7 @@ data class PlaybackSettings(
|
||||||
val contentScale: ContentScale,
|
val contentScale: ContentScale,
|
||||||
val subtitleDelay: Duration,
|
val subtitleDelay: Duration,
|
||||||
val hasSubtitleDownloadPermission: Boolean,
|
val hasSubtitleDownloadPermission: Boolean,
|
||||||
|
val playbackSpeedEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -137,6 +143,7 @@ fun PlaybackDialog(
|
||||||
data = PlaybackDialogType.PLAYBACK_SPEED,
|
data = PlaybackDialogType.PLAYBACK_SPEED,
|
||||||
headline = stringResource(R.string.playback_speed),
|
headline = stringResource(R.string.playback_speed),
|
||||||
supporting = settings.playbackSpeed.toString(),
|
supporting = settings.playbackSpeed.toString(),
|
||||||
|
enabled = settings.playbackSpeedEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (enableVideoScale) {
|
if (enableVideoScale) {
|
||||||
|
|
@ -395,6 +402,14 @@ fun StreamChoiceBottomDialog(
|
||||||
gravity: Int,
|
gravity: Int,
|
||||||
currentChoice: Int? = null,
|
currentChoice: Int? = null,
|
||||||
) {
|
) {
|
||||||
|
val focusRequesters = remember(choices.size) { List(choices.size) { FocusRequester() } }
|
||||||
|
if (currentChoice != null) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
choices.indexOfFirstOrNull { it.index == currentChoice }?.let {
|
||||||
|
focusRequesters.getOrNull(it)?.tryRequestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO enforcing a width ends up ignore the gravity
|
// TODO enforcing a width ends up ignore the gravity
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
@ -445,6 +460,7 @@ fun StreamChoiceBottomDialog(
|
||||||
if (choice.streamTitle != null) Text(choice.displayTitle)
|
if (choice.streamTitle != null) Text(choice.displayTitle)
|
||||||
},
|
},
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[index]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -637,6 +637,9 @@ fun PlaybackPageContent(
|
||||||
subtitleDelay = subtitleDelay,
|
subtitleDelay = subtitleDelay,
|
||||||
hasSubtitleDownloadPermission =
|
hasSubtitleDownloadPermission =
|
||||||
remember(userDto) { userDto?.policy?.let { it.isAdministrator || it.enableSubtitleManagement } == true },
|
remember(userDto) { userDto?.policy?.let { it.isAdministrator || it.enableSubtitleManagement } == true },
|
||||||
|
// TODO Passing through audio prevents changing playback speed
|
||||||
|
// See https://github.com/damontecres/Wholphin/issues/164
|
||||||
|
playbackSpeedEnabled = playerBackend == PlayerBackend.MPV || currentPlayback?.audioDecoder != null,
|
||||||
),
|
),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
playbackDialog = null
|
playbackDialog = null
|
||||||
|
|
|
||||||
|
|
@ -449,11 +449,20 @@ class PlaybackViewModel
|
||||||
|
|
||||||
// Create the correct player for the media
|
// Create the correct player for the media
|
||||||
createPlayer(videoStream?.hdr == true, videoStream?.is4k == true)
|
createPlayer(videoStream?.hdr == true, videoStream?.is4k == true)
|
||||||
|
val subtitleLanguagePreference =
|
||||||
|
serverRepository.currentUserDto.value
|
||||||
|
?.configuration
|
||||||
|
?.subtitleLanguagePreference
|
||||||
val subtitleStreams =
|
val subtitleStreams =
|
||||||
mediaSource.mediaStreams
|
mediaSource.mediaStreams
|
||||||
?.filter { it.type == MediaStreamType.SUBTITLE }
|
?.filter { it.type == MediaStreamType.SUBTITLE }
|
||||||
?.map {
|
.let {
|
||||||
|
if (subtitleLanguagePreference.isNotNullOrBlank()) {
|
||||||
|
it?.sortedByDescending { it.language != null && subtitleLanguagePreference == it.language }
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}?.map {
|
||||||
SimpleMediaStream.from(context, it, true)
|
SimpleMediaStream.from(context, it, true)
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ fun PreferencesContent(
|
||||||
try {
|
try {
|
||||||
System.loadLibrary("mpv")
|
System.loadLibrary("mpv")
|
||||||
System.loadLibrary("player")
|
System.loadLibrary("player")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: UnsatisfiedLinkError) {
|
||||||
Timber.w(ex, "Could not load libmpv")
|
Timber.w(ex, "Could not load libmpv")
|
||||||
showToast(context, "MPV is not supported on this device")
|
showToast(context, "MPV is not supported on this device")
|
||||||
viewModel.preferenceDataStore.updateData {
|
viewModel.preferenceDataStore.updateData {
|
||||||
|
|
@ -400,7 +400,7 @@ fun PreferencesContent(
|
||||||
when (val conn = seerrConnection) {
|
when (val conn = seerrConnection) {
|
||||||
is SeerrConnectionStatus.Error -> {
|
is SeerrConnectionStatus.Error -> {
|
||||||
SeerrDialogMode.Error(
|
SeerrDialogMode.Error(
|
||||||
conn.serverUrl,
|
conn.server.url,
|
||||||
conn.ex,
|
conn.ex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ class SwitchSeerrViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
serverConnectionStatus.update { LoadingState.Success }
|
||||||
} else {
|
} else {
|
||||||
val message =
|
val message =
|
||||||
results
|
results
|
||||||
|
|
@ -126,37 +127,50 @@ class SwitchSeerrViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createUrls(url: String): List<HttpUrl> {
|
fun createUrls(url: String): List<HttpUrl> {
|
||||||
val urls = mutableListOf<String>()
|
val urls = mutableListOf<HttpUrl>()
|
||||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
urls.add(url)
|
|
||||||
val httpUrl = url.toHttpUrl()
|
val httpUrl = url.toHttpUrl()
|
||||||
|
urls.add(httpUrl)
|
||||||
if (HttpUrl.defaultPort(httpUrl.scheme) == httpUrl.port) {
|
if (HttpUrl.defaultPort(httpUrl.scheme) == httpUrl.port) {
|
||||||
urls.add("$url:5055")
|
urls.add(httpUrl.newBuilder().port(5055).build())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
urls.add("http://$url")
|
|
||||||
val httpUrl = "http://$url".toHttpUrl()
|
val httpUrl = "http://$url".toHttpUrl()
|
||||||
|
urls.add(httpUrl)
|
||||||
if (httpUrl.port == 80) {
|
if (httpUrl.port == 80) {
|
||||||
urls.add("https://$url")
|
urls.add(httpUrl.newBuilder().scheme("https").build())
|
||||||
urls.add("http://$url:5055")
|
urls.add(httpUrl.newBuilder().port(5055).build())
|
||||||
urls.add("https://$url:5055")
|
urls.add(
|
||||||
|
httpUrl
|
||||||
|
.newBuilder()
|
||||||
|
.scheme("https")
|
||||||
|
.port(5055)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
urls.add("https://$url")
|
urls.add(httpUrl.newBuilder().scheme("https").build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return urls.map { cleanUrl(it).toHttpUrl() }
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanUrl(url: String) =
|
fun createSeerrApiUrl(url: String): String =
|
||||||
if (!url.endsWith("/api/v1")) {
|
if (url.isBlank()) {
|
||||||
|
url
|
||||||
|
} else if (url.endsWith("/api/v1") || url.endsWith("/api/v1/")) {
|
||||||
|
url
|
||||||
|
} else {
|
||||||
url
|
url
|
||||||
.toHttpUrl()
|
.toHttpUrl()
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.apply {
|
.addPathSegment("api")
|
||||||
addPathSegment("api")
|
.addPathSegment("v1")
|
||||||
addPathSegment("v1")
|
.build()
|
||||||
}.build()
|
|
||||||
.toString()
|
.toString()
|
||||||
} else {
|
|
||||||
url
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun migrateSeerrUrl(url: String): String {
|
||||||
|
var url = url.removeSuffix("/api/v1/").removeSuffix("/api/v1")
|
||||||
|
if (!url.endsWith("/")) url += "/"
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
|
@ -53,13 +52,16 @@ import androidx.media3.ui.compose.modifiers.resizeWithContentScale
|
||||||
import androidx.media3.ui.compose.state.rememberPresentationState
|
import androidx.media3.ui.compose.state.rememberPresentationState
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
|
import coil3.annotation.ExperimentalCoilApi
|
||||||
import coil3.compose.SubcomposeAsyncImage
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
|
import coil3.compose.useExistingImageAsPlaceholder
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import coil3.request.crossfade
|
import coil3.request.transitionFactory
|
||||||
import coil3.size.Size
|
import coil3.size.Size
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.VideoFilter
|
import com.github.damontecres.wholphin.data.model.VideoFilter
|
||||||
import com.github.damontecres.wholphin.ui.AppColors
|
import com.github.damontecres.wholphin.ui.AppColors
|
||||||
|
import com.github.damontecres.wholphin.ui.CrossFadeFactory
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
|
@ -71,12 +73,14 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import org.jellyfin.sdk.model.api.MediaType
|
import org.jellyfin.sdk.model.api.MediaType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
private const val TAG = "ImagePage"
|
private const val TAG = "ImagePage"
|
||||||
private const val DEBUG = false
|
private const val DEBUG = false
|
||||||
|
|
||||||
@SuppressLint("ConfigurationScreenWidthHeight")
|
@SuppressLint("ConfigurationScreenWidthHeight")
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
@kotlin.OptIn(ExperimentalCoilApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SlideshowPage(
|
fun SlideshowPage(
|
||||||
slideshow: Destination.Slideshow,
|
slideshow: Destination.Slideshow,
|
||||||
|
|
@ -107,7 +111,7 @@ fun SlideshowPage(
|
||||||
val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter())
|
val imageFilter by viewModel.imageFilter.observeAsState(VideoFilter())
|
||||||
val position by viewModel.position.observeAsState(0)
|
val position by viewModel.position.observeAsState(0)
|
||||||
val pager by viewModel.pager.observeAsState()
|
val pager by viewModel.pager.observeAsState()
|
||||||
val imageState by viewModel.image.observeAsState()
|
// val imageState by viewModel.image.observeAsState()
|
||||||
|
|
||||||
var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) }
|
var zoomFactor by rememberSaveable { mutableFloatStateOf(1f) }
|
||||||
val isZoomed = zoomFactor * 100 > 102
|
val isZoomed = zoomFactor * 100 > 102
|
||||||
|
|
@ -211,9 +215,6 @@ fun SlideshowPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(imageState) {
|
|
||||||
reset(true)
|
|
||||||
}
|
|
||||||
val player = viewModel.player
|
val player = viewModel.player
|
||||||
val presentationState = rememberPresentationState(player)
|
val presentationState = rememberPresentationState(player)
|
||||||
LaunchedEffect(slideshowActive) {
|
LaunchedEffect(slideshowActive) {
|
||||||
|
|
@ -223,16 +224,6 @@ fun SlideshowPage(
|
||||||
|
|
||||||
var longPressing by remember { mutableStateOf(false) }
|
var longPressing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val contentModifier =
|
|
||||||
Modifier
|
|
||||||
.clickable(
|
|
||||||
interactionSource = null,
|
|
||||||
indication = null,
|
|
||||||
onClick = {
|
|
||||||
showOverlay = !showOverlay
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
|
@ -317,17 +308,20 @@ fun SlideshowPage(
|
||||||
result
|
result
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
when (loadingState) {
|
when (val st = loadingState) {
|
||||||
ImageLoadingState.Error -> {
|
ImageLoadingState.Error -> {
|
||||||
ErrorMessage("Error loading image", null, modifier)
|
ErrorMessage("Error loading image", null, modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageLoadingState.Loading -> {
|
ImageLoadingState.Loading -> {
|
||||||
LoadingPage(modifier)
|
LoadingPage(modifier, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ImageLoadingState.Success -> {
|
is ImageLoadingState.Success -> {
|
||||||
imageState?.let { imageState ->
|
val imageState = st.image
|
||||||
|
LaunchedEffect(imageState) {
|
||||||
|
reset(true)
|
||||||
|
}
|
||||||
if (imageState.image.data.mediaType == MediaType.VIDEO) {
|
if (imageState.image.data.mediaType == MediaType.VIDEO) {
|
||||||
LaunchedEffect(imageState.id) {
|
LaunchedEffect(imageState.id) {
|
||||||
val mediaItem =
|
val mediaItem =
|
||||||
|
|
@ -353,7 +347,7 @@ fun SlideshowPage(
|
||||||
}
|
}
|
||||||
val contentScale = ContentScale.Fit
|
val contentScale = ContentScale.Fit
|
||||||
val scaledModifier =
|
val scaledModifier =
|
||||||
contentModifier.resizeWithContentScale(
|
Modifier.resizeWithContentScale(
|
||||||
contentScale,
|
contentScale,
|
||||||
presentationState.videoSizeDp,
|
presentationState.videoSizeDp,
|
||||||
)
|
)
|
||||||
|
|
@ -391,7 +385,7 @@ fun SlideshowPage(
|
||||||
val showLoadingThumbnail = true
|
val showLoadingThumbnail = true
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
modifier =
|
modifier =
|
||||||
contentModifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
scaleX = zoomAnimation
|
scaleX = zoomAnimation
|
||||||
|
|
@ -417,7 +411,8 @@ fun SlideshowPage(
|
||||||
.Builder(LocalContext.current)
|
.Builder(LocalContext.current)
|
||||||
.data(imageState.url)
|
.data(imageState.url)
|
||||||
.size(Size.ORIGINAL)
|
.size(Size.ORIGINAL)
|
||||||
.crossfade(!showLoadingThumbnail)
|
.transitionFactory(CrossFadeFactory(750.milliseconds))
|
||||||
|
.useExistingImageAsPlaceholder(true)
|
||||||
.build(),
|
.build(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
|
|
@ -431,14 +426,6 @@ fun SlideshowPage(
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
loading = {
|
|
||||||
ImageLoadingPlaceholder(
|
|
||||||
thumbnailUrl = imageState.thumbnailUrl,
|
|
||||||
showThumbnail = showLoadingThumbnail,
|
|
||||||
colorFilter = colorFilter,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// Ensure that if an image takes a long time to load, it won't be skipped
|
// Ensure that if an image takes a long time to load, it won't be skipped
|
||||||
onLoading = {
|
onLoading = {
|
||||||
viewModel.pulseSlideshow(Long.MAX_VALUE)
|
viewModel.pulseSlideshow(Long.MAX_VALUE)
|
||||||
|
|
@ -463,17 +450,22 @@ fun SlideshowPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
showOverlay,
|
showOverlay,
|
||||||
enter = slideInVertically { it },
|
enter = slideInVertically { it },
|
||||||
exit = slideOutVertically { it },
|
exit = slideOutVertically { it },
|
||||||
modifier = Modifier.align(Alignment.BottomStart),
|
modifier = Modifier.align(Alignment.BottomStart),
|
||||||
) {
|
) {
|
||||||
imageState?.let { imageState ->
|
when (val st = loadingState) {
|
||||||
|
ImageLoadingState.Error -> {}
|
||||||
|
|
||||||
|
ImageLoadingState.Loading -> {}
|
||||||
|
|
||||||
|
is ImageLoadingState.Success -> {
|
||||||
|
val imageState = st.image
|
||||||
ImageOverlay(
|
ImageOverlay(
|
||||||
modifier =
|
modifier =
|
||||||
contentModifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(AppColors.TransparentBlack50),
|
.background(AppColors.TransparentBlack50),
|
||||||
onDismiss = { showOverlay = false },
|
onDismiss = { showOverlay = false },
|
||||||
|
|
@ -496,6 +488,7 @@ fun SlideshowPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
AnimatedVisibility(showFilterDialog) {
|
AnimatedVisibility(showFilterDialog) {
|
||||||
ImageFilterDialog(
|
ImageFilterDialog(
|
||||||
filter = imageFilter,
|
filter = imageFilter,
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,6 @@ class SlideshowViewModel
|
||||||
|
|
||||||
var slideshowDelay by Delegates.notNull<Long>()
|
var slideshowDelay by Delegates.notNull<Long>()
|
||||||
|
|
||||||
// private val album = MutableLiveData<BaseItem>()
|
|
||||||
private val _pager = MutableLiveData<ApiRequestPager<GetItemsRequest>>()
|
private val _pager = MutableLiveData<ApiRequestPager<GetItemsRequest>>()
|
||||||
val pager: LiveData<List<BaseItem?>> = _pager.map { it }
|
val pager: LiveData<List<BaseItem?>> = _pager.map { it }
|
||||||
val position = MutableLiveData(0)
|
val position = MutableLiveData(0)
|
||||||
|
|
@ -148,7 +147,7 @@ class SlideshowViewModel
|
||||||
parentId = slideshowSettings.parentId,
|
parentId = slideshowSettings.parentId,
|
||||||
includeItemTypes = includeItemTypes,
|
includeItemTypes = includeItemTypes,
|
||||||
fields = PhotoItemFields,
|
fields = PhotoItemFields,
|
||||||
recursive = true,
|
recursive = slideshowSettings.recursive,
|
||||||
sortBy = listOf(slideshowSettings.sortAndDirection.sort),
|
sortBy = listOf(slideshowSettings.sortAndDirection.sort),
|
||||||
sortOrder = listOf(slideshowSettings.sortAndDirection.direction),
|
sortOrder = listOf(slideshowSettings.sortAndDirection.direction),
|
||||||
),
|
),
|
||||||
|
|
@ -205,7 +204,8 @@ class SlideshowViewModel
|
||||||
_pager.value?.let { pager ->
|
_pager.value?.let { pager ->
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
val image = pager.getBlocking(position)
|
val image =
|
||||||
|
if (position in pager.indices) pager.getBlocking(position) else null
|
||||||
Timber.v("Got image for $position: ${image != null}")
|
Timber.v("Got image for $position: ${image != null}")
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
this@SlideshowViewModel.position.setValueOnMain(position)
|
this@SlideshowViewModel.position.setValueOnMain(position)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ data class Clock(
|
||||||
fun ProvideLocalClock(content: @Composable () -> Unit) {
|
fun ProvideLocalClock(content: @Composable () -> Unit) {
|
||||||
val clock = remember { Clock() }
|
val clock = remember { Clock() }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.Default) {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val time = TimeFormatter.format(now)
|
val time = TimeFormatter.format(now)
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,13 @@ object StreamFormatting {
|
||||||
context.getString(R.string.dolby_atmos)
|
context.getString(R.string.dolby_atmos)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile?.contains("DTS:X", true) == true -> {
|
profile?.contains("DTS", true) == true -> {
|
||||||
context.getString(R.string.dts_x)
|
when {
|
||||||
|
profile.contains("X", true) -> context.getString(R.string.dts_x)
|
||||||
|
profile.contains("MA", true) -> context.getString(R.string.dts_hd_ma)
|
||||||
|
profile.contains("HD", true) -> context.getString(R.string.dts_hd)
|
||||||
|
else -> context.getString(R.string.dts)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile?.contains("DTS:HD", true) == true -> {
|
|
||||||
context.getString(R.string.dts_hd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ class ApiRequestPager<T>(
|
||||||
position: Int,
|
position: Int,
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
) {
|
) {
|
||||||
|
mutex.withLock {
|
||||||
val item =
|
val item =
|
||||||
api.userLibraryApi.getItem(itemId).content.let {
|
api.userLibraryApi.getItem(itemId).content.let {
|
||||||
BaseItem.from(
|
BaseItem.from(
|
||||||
|
|
@ -156,7 +157,6 @@ class ApiRequestPager<T>(
|
||||||
}
|
}
|
||||||
val pageNumber = position / pageSize
|
val pageNumber = position / pageSize
|
||||||
val index = position - pageNumber * pageSize
|
val index = position - pageNumber * pageSize
|
||||||
mutex.withLock {
|
|
||||||
val page = cachedPages.getIfPresent(pageNumber)
|
val page = cachedPages.getIfPresent(pageNumber)
|
||||||
if (page != null && index in page.indices) {
|
if (page != null && index in page.indices) {
|
||||||
page[index] = item
|
page[index] = item
|
||||||
|
|
|
||||||
131
app/src/main/res/values-ar/strings.xml
Normal file
131
app/src/main/res/values-ar/strings.xml
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="about">حول</string>
|
||||||
|
<string name="active_recordings">التسجيلات النشطة</string>
|
||||||
|
<string name="add_favorite">إضافة للمفضلة</string>
|
||||||
|
<string name="add_server">إضافة خادم</string>
|
||||||
|
<string name="add_user">إضافة مستخدم</string>
|
||||||
|
<string name="audio">الصوت</string>
|
||||||
|
<string name="birthplace">مكان الميلاد</string>
|
||||||
|
<string name="bitrate">معدل البت</string>
|
||||||
|
<string name="born">وُلد</string>
|
||||||
|
<string name="cancel_recording">إلغاء التسجيل</string>
|
||||||
|
<string name="cancel_series_recording">إلغاء تسجيل المسلسل</string>
|
||||||
|
<string name="cancel">إلغاء</string>
|
||||||
|
<string name="chapters">الفصول</string>
|
||||||
|
<string name="choose_stream">اختيار %1$s</string>
|
||||||
|
<string name="clear_image_cache">مسح ذاكرة التخزين المؤقت للصور</string>
|
||||||
|
<string name="collection">مجموعة</string>
|
||||||
|
<string name="collections">المجموعات</string>
|
||||||
|
<string name="commercial">تجاري</string>
|
||||||
|
<string name="community_rating">تقييم المجتمع</string>
|
||||||
|
<string name="compose_error_message_title">حدث خطأ! اضغط على الزر لإرسال السجلات إلى خادمك.</string>
|
||||||
|
<string name="confirm">تأكيد</string>
|
||||||
|
<string name="continue_watching">تابع المشاهدة</string>
|
||||||
|
<string name="critic_rating">تقييم النقاد</string>
|
||||||
|
<string name="decimal_seconds">%.2f ثانية</string>
|
||||||
|
<string name="default_track">الافتراضي</string>
|
||||||
|
<string name="delete">حذف</string>
|
||||||
|
<string name="died">تُوفي</string>
|
||||||
|
<string name="directed_by">إخراج %1$s</string>
|
||||||
|
<string name="director">المخرج</string>
|
||||||
|
<string name="disabled">مُعطّل</string>
|
||||||
|
<string name="discovered_servers">الخوادم المكتشفة</string>
|
||||||
|
<string name="download_and_update"><![CDATA[تنزيل وتحديث]]></string>
|
||||||
|
<string name="downloading">تنزيل…</string>
|
||||||
|
<string name="enabled">مُفعّل</string>
|
||||||
|
<string name="ends_at">ينتهي عند %1$s</string>
|
||||||
|
<string name="enter_server_url">أدخل عنوان IP للخادم أو رابط URL</string>
|
||||||
|
<string name="enter_server_address">أدخل عنوان الخادم</string>
|
||||||
|
<string name="episodes">الحلقات</string>
|
||||||
|
<string name="error_loading_collection">خطأ في تحميل المجموعة %1$s</string>
|
||||||
|
<string name="external_track">خارجي</string>
|
||||||
|
<string name="favorites">المفضلة</string>
|
||||||
|
<string name="file_size">الحجم</string>
|
||||||
|
<string name="forced_track">إجبارية</string>
|
||||||
|
<string name="genres">الأنواع</string>
|
||||||
|
<string name="go_to_series">انتقل إلى المسلسل</string>
|
||||||
|
<string name="go_to">انتقل إلى</string>
|
||||||
|
<string name="hide_controller_timeout">إخفاء عناصر التحكم في التشغيل</string>
|
||||||
|
<string name="hide_debug_info">إخفاء معلومات تصحيح الأخطاء</string>
|
||||||
|
<string name="hide">إخفاء</string>
|
||||||
|
<string name="home">الرئيسية</string>
|
||||||
|
<string name="immediate">فوري</string>
|
||||||
|
<string name="intro">المقدمة</string>
|
||||||
|
<string name="jump_letters">#ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||||
|
<string name="library">المكتبة</string>
|
||||||
|
<string name="license_info">معلومات الترخيص</string>
|
||||||
|
<string name="live_tv">تلفزيون مباشر</string>
|
||||||
|
<string name="loading">تحميل…</string>
|
||||||
|
<string name="mark_entire_series_as_played">هل تريد تعيين المسلسل بأكمله كمُشاهَد؟</string>
|
||||||
|
<string name="mark_entire_series_as_unplayed">هل تريد تعيين المسلسل بأكمله كغير مشاهد؟</string>
|
||||||
|
<string name="mark_unwatched">تعيين كغير مشاهد</string>
|
||||||
|
<string name="mark_watched">تعيين كمشاهد</string>
|
||||||
|
<string name="max_bitrate">الحد الأقصى لمعدل البت</string>
|
||||||
|
<string name="more_like_this">المزيد من هذا القبيل</string>
|
||||||
|
<string name="more">المزيد</string>
|
||||||
|
<plurals name="movies">
|
||||||
|
<item quantity="zero">الأفلام</item>
|
||||||
|
<item quantity="one"></item>
|
||||||
|
<item quantity="two"></item>
|
||||||
|
<item quantity="few"></item>
|
||||||
|
<item quantity="many"></item>
|
||||||
|
<item quantity="other"></item>
|
||||||
|
</plurals>
|
||||||
|
<string name="name">الاسم</string>
|
||||||
|
<string name="next_up">التالي</string>
|
||||||
|
<string name="no_data">لا توجد بيانات</string>
|
||||||
|
<string name="no_results">لا توجد نتائج</string>
|
||||||
|
<string name="no_servers_found">لم يتم العثور على خوادم</string>
|
||||||
|
<string name="no_scheduled_recordings">لا توجد تسجيلات مجدولة</string>
|
||||||
|
<string name="no_update_available">لا يوجد تحديث متاح</string>
|
||||||
|
<string name="none">لا شيء</string>
|
||||||
|
<string name="only_forced_subtitles">الترجمات الإجبارية فقط</string>
|
||||||
|
<string name="outro">خاتمة</string>
|
||||||
|
<string name="path">المسار</string>
|
||||||
|
<plurals name="people">
|
||||||
|
<item quantity="zero">الأشخاص</item>
|
||||||
|
<item quantity="one"></item>
|
||||||
|
<item quantity="two"></item>
|
||||||
|
<item quantity="few"></item>
|
||||||
|
<item quantity="many"></item>
|
||||||
|
<item quantity="other"></item>
|
||||||
|
</plurals>
|
||||||
|
<string name="play_count">عدد مرات التشغيل</string>
|
||||||
|
<string name="play_from_here">تشغيل من هنا</string>
|
||||||
|
<string name="play">تشغيل</string>
|
||||||
|
<string name="playback_debug_info">إظهار معلومات التشغيل التفصيلية</string>
|
||||||
|
<string name="playback_speed">سرعة التشغيل</string>
|
||||||
|
<string name="playback">التشغيل</string>
|
||||||
|
<string name="playlist">قائمة التشغيل</string>
|
||||||
|
<string name="playlists">قوائم التشغيل</string>
|
||||||
|
<string name="preview">معاينة</string>
|
||||||
|
<string name="profile_specific_settings">إعدادات ملف تعريف المستخدم</string>
|
||||||
|
<string name="queue">قائمة الانتظار</string>
|
||||||
|
<string name="recap">ملخص ما سبق</string>
|
||||||
|
<string name="recently_added_in">أُضيف مؤخرًا في %1$s</string>
|
||||||
|
<string name="recently_added">أُضيف مؤخرًا</string>
|
||||||
|
<string name="recently_recorded">التسجيلات الأخيرة</string>
|
||||||
|
<string name="recently_released">أحدث الإصدارات</string>
|
||||||
|
<string name="recommended">مقترح</string>
|
||||||
|
<string name="record_program">تسجيل البرنامج</string>
|
||||||
|
<string name="record_series">تسجيل المسلسل</string>
|
||||||
|
<string name="remove_favorite">إزالة من المفضلة</string>
|
||||||
|
<string name="restart">إعادة تشغيل</string>
|
||||||
|
<string name="resume">استئناف</string>
|
||||||
|
<string name="save">حفظ</string>
|
||||||
|
<string name="search_and_download"><![CDATA[بحث وتنزيل]]></string>
|
||||||
|
<string name="search">بحث</string>
|
||||||
|
<string name="searching">جارٍ البحث…</string>
|
||||||
|
<string name="voice_search">البحث الصوتي</string>
|
||||||
|
<string name="voice_starting">جارٍ البدء</string>
|
||||||
|
<string name="voice_search_prompt">تحدث للبحث</string>
|
||||||
|
<string name="processing">جارٍ المعالجة</string>
|
||||||
|
<string name="press_back_to_cancel">اضغط رجوع للإلغاء</string>
|
||||||
|
<string name="voice_error_audio">خطأ في تسجيل الصوت</string>
|
||||||
|
<string name="voice_error_client">خطأ في التعرف على الصوت</string>
|
||||||
|
<string name="voice_error_permissions">مطلوب إذن الوصول للميكروفون</string>
|
||||||
|
<string name="voice_error_network">خطأ في الشبكة</string>
|
||||||
|
<string name="voice_error_network_timeout">انتهت مهلة الشبكة</string>
|
||||||
|
<string name="voice_error_no_match">لم يتم التعرف على الكلام</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -401,6 +401,7 @@
|
||||||
<string name="clear_track_choices">Vyčistit výběr skladeb</string>
|
<string name="clear_track_choices">Vyčistit výběr skladeb</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">True HD</string>
|
<string name="truehd">True HD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,7 @@
|
||||||
<string name="clear_track_choices">Ryd valg af spor</string>
|
<string name="clear_track_choices">Ryd valg af spor</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
|
||||||
|
|
@ -199,12 +199,12 @@
|
||||||
<string name="italic_font">Kursivschrift</string>
|
<string name="italic_font">Kursivschrift</string>
|
||||||
<string name="runtime_sort">Laufzeit</string>
|
<string name="runtime_sort">Laufzeit</string>
|
||||||
<plurals name="theme_songs">
|
<plurals name="theme_songs">
|
||||||
<item quantity="one">Theme Songs</item>
|
<item quantity="one">Titelsong</item>
|
||||||
<item quantity="other"/>
|
<item quantity="other">Titelsongs</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="theme_videos">
|
<plurals name="theme_videos">
|
||||||
<item quantity="one">Theme Videos</item>
|
<item quantity="one">Titelvideo</item>
|
||||||
<item quantity="other"/>
|
<item quantity="other">Titelvideos</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="clips">
|
<plurals name="clips">
|
||||||
<item quantity="one">Clip</item>
|
<item quantity="one">Clip</item>
|
||||||
|
|
@ -224,11 +224,11 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="shorts">
|
<plurals name="shorts">
|
||||||
<item quantity="one">Shorts</item>
|
<item quantity="one">Shorts</item>
|
||||||
<item quantity="other"/>
|
<item quantity="other">Kurzvideos</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="combine_continue_next_summary">Trifft nur auf Serien zu</string>
|
<string name="combine_continue_next_summary">Trifft nur auf Serien zu</string>
|
||||||
<string name="direct_play_ass">Direct play ASS subtitles</string>
|
<string name="direct_play_ass">Libass für ASS Untertitel verwenden</string>
|
||||||
<string name="direct_play_pgs">Direct play PGS subtitles</string>
|
<string name="direct_play_pgs">Direktwiedergabe PGS Untertitel</string>
|
||||||
<string name="downmix_stereo">Immer zu Stereo heruntermischen</string>
|
<string name="downmix_stereo">Immer zu Stereo heruntermischen</string>
|
||||||
<string name="ffmpeg_extension_pref">FFmpeg decoder Modul verwenden</string>
|
<string name="ffmpeg_extension_pref">FFmpeg decoder Modul verwenden</string>
|
||||||
<string name="global_content_scale">Standard Skalierung</string>
|
<string name="global_content_scale">Standard Skalierung</string>
|
||||||
|
|
@ -251,8 +251,8 @@
|
||||||
<string name="font_size">Schriftgröße</string>
|
<string name="font_size">Schriftgröße</string>
|
||||||
<string name="font_color">Schriftfarbe</string>
|
<string name="font_color">Schriftfarbe</string>
|
||||||
<string name="font_opacity">Schriftart Deckkraft</string>
|
<string name="font_opacity">Schriftart Deckkraft</string>
|
||||||
<string name="edge_style">Rand Stil</string>
|
<string name="edge_style">Stil der Umrandung</string>
|
||||||
<string name="edge_color">Rand Farbe</string>
|
<string name="edge_color">Farbe der Umrandung</string>
|
||||||
<string name="background_opacity">Hintergrund Deckktraft</string>
|
<string name="background_opacity">Hintergrund Deckktraft</string>
|
||||||
<string name="background_style">Hintergrund Stil</string>
|
<string name="background_style">Hintergrund Stil</string>
|
||||||
<string name="subtitle_style">Untertitel Stil</string>
|
<string name="subtitle_style">Untertitel Stil</string>
|
||||||
|
|
@ -329,7 +329,7 @@
|
||||||
<string name="use_server_credentials">Login via Server</string>
|
<string name="use_server_credentials">Login via Server</string>
|
||||||
<string name="press_enter_to_confirm">Drücke die mittlere Taste zur Bestätigung</string>
|
<string name="press_enter_to_confirm">Drücke die mittlere Taste zur Bestätigung</string>
|
||||||
<string name="image_cache_size">Größe des Festplatten-Caches für Bilder (MB)</string>
|
<string name="image_cache_size">Größe des Festplatten-Caches für Bilder (MB)</string>
|
||||||
<string name="cast_and_crew">Besetzung & Mitwirkende</string>
|
<string name="cast_and_crew"><![CDATA[Besetzung & Mitwirkende]]></string>
|
||||||
<string name="container">Container</string>
|
<string name="container">Container</string>
|
||||||
<string name="codec">Codec</string>
|
<string name="codec">Codec</string>
|
||||||
<string name="level">Level</string>
|
<string name="level">Level</string>
|
||||||
|
|
@ -365,6 +365,7 @@
|
||||||
<string name="request_4k">In 4K anfragen</string>
|
<string name="request_4k">In 4K anfragen</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -432,4 +433,122 @@
|
||||||
<string name="play_videos_during_slideshow">Videos während der Diashow abspielen</string>
|
<string name="play_videos_during_slideshow">Videos während der Diashow abspielen</string>
|
||||||
<string name="max_days_next_up">Maximale Tage in \"Als nächstes\"</string>
|
<string name="max_days_next_up">Maximale Tage in \"Als nächstes\"</string>
|
||||||
<string name="quick_connect">Quick Connect</string>
|
<string name="quick_connect">Quick Connect</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s Minute</item>
|
||||||
|
<item quantity="other">%s Minuten</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="interlaced">Interlaced</string>
|
||||||
|
<string name="nal">NAL</string>
|
||||||
|
<string name="quick_connect_summary">Gewähre anderen Geräten Zugriff auf deinen Account</string>
|
||||||
|
<string name="quick_connect_code">Eingabe Quick Connect Code</string>
|
||||||
|
<string name="no_limit">Keine Beschränkung</string>
|
||||||
|
<string name="add_row">Reihe hinzufügen</string>
|
||||||
|
<string name="genres_in">Genres in %1$s</string>
|
||||||
|
<string name="recently_released_in">kürzlich veröffentlicht in %1$s</string>
|
||||||
|
<string name="height">Höhe</string>
|
||||||
|
<string name="apply_all_rows">Auf alle Reihen anwenden</string>
|
||||||
|
<string name="add_row_for">Reihe hinzufügen für %1$s</string>
|
||||||
|
<string name="overwrite_server_settings">Überschreibe Einstellungen auf dem Server?</string>
|
||||||
|
<string name="overwrite_local_settings">Überschreibe lokale Einstellungen?</string>
|
||||||
|
<string name="suggestions_for">Vorschläge für %1$s</string>
|
||||||
|
<string name="send_media_info_log_to_server">Sende Media Info Log zum Server</string>
|
||||||
|
<string name="customize_home">Anpassung Homepage</string>
|
||||||
|
<string name="for_episodes">Für Episoden</string>
|
||||||
|
<string name="display_presets_description">Vorlagen um schnell alle Reihen zu gestalten</string>
|
||||||
|
<string name="customize_home_summary">Wähle Reihen und Bilder auf der Homepage</string>
|
||||||
|
<string name="start_after">Starte anschließend</string>
|
||||||
|
<string name="display_preset_series_thumb">Serien Vorschaubilder</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Episoden Vorschaubilder</string>
|
||||||
|
<string name="seerr_login">Einloggen zum Seerr Server</string>
|
||||||
|
<string name="seerr_jellyfin_user">Jellyfin User</string>
|
||||||
|
<string name="show_media_management">Zeige Media Management Optionen</string>
|
||||||
|
<string name="decrease_all_cards_size">Größe für alle Karten reduzieren</string>
|
||||||
|
<string name="settings_saved">Einstellungen gespeichert</string>
|
||||||
|
<string name="max_age_rating">Maximale Altersfreigabe</string>
|
||||||
|
<string name="no_max">Keine Begrenzung</string>
|
||||||
|
<string name="for_all_ages">Ohne Altersbeschränkung</string>
|
||||||
|
<string name="up_to_age">Ab %s Jahre</string>
|
||||||
|
<string name="screensaver">Bildschirmschoner</string>
|
||||||
|
<string name="screensaver_settings">Bildschirmschoner-Einstellungen</string>
|
||||||
|
<string name="start_screensaver">Bildschirmschoner starten</string>
|
||||||
|
<string name="duration">Länge</string>
|
||||||
|
<string name="photos">Fotos</string>
|
||||||
|
<string name="include_types">Arten einschließen</string>
|
||||||
|
<string name="include_types_summary">Für welche Arten von Einträgen sollen Bilder angezeigt werden</string>
|
||||||
|
<string name="content_scale_fit">Angepasst</string>
|
||||||
|
<string name="content_scale_crop">Zugeschnitten</string>
|
||||||
|
<string name="content_scale_fill">Ausgefüllt</string>
|
||||||
|
<string name="skip_ignore">Ignorieren</string>
|
||||||
|
<string name="skip_automatically">Automatisch überspringen</string>
|
||||||
|
<string name="skip_ask">Vor dem Überspringen nachfragen</string>
|
||||||
|
<string name="ffmpeg_fallback">FFmpeg nur verwenden, sofern keine anderen Decoder vorhanden sind</string>
|
||||||
|
<string name="ffmpeg_prefer">FFmpeg statt der vorhandenen Decoder verwenden</string>
|
||||||
|
<string name="ffmpeg_never">FFmpeg Decoder nie verwenden</string>
|
||||||
|
<string name="next_up_playback_end">Am Ende einer Playlist</string>
|
||||||
|
<string name="next_up_outro">Während der Credits/Outro</string>
|
||||||
|
<string name="white">Weiß</string>
|
||||||
|
<string name="light_gray">Hellgrau</string>
|
||||||
|
<string name="dark_gray">Dunkelgrau</string>
|
||||||
|
<string name="yellow">Gelb</string>
|
||||||
|
<string name="cyan">Cyan</string>
|
||||||
|
<string name="magenta">Magenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Umrandung</string>
|
||||||
|
<string name="subtitle_edge_shadow">Schatten</string>
|
||||||
|
<string name="prefer_mpv">MPV bevorzugen</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">ExoPlayer für die HDR Wiedergabe verwenden</string>
|
||||||
|
<string name="aspect_ratios_poster">Poster (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Quadrat (1:1)</string>
|
||||||
|
<string name="image_type_primary">Primär</string>
|
||||||
|
<string name="backdrop_style_dynamic">Bild mit dynamischen Farben</string>
|
||||||
|
<string name="api_key">API Schlüssel</string>
|
||||||
|
<string name="seerr_local_user">Lokaler Benutzer</string>
|
||||||
|
<string name="search_for">Suche %s</string>
|
||||||
|
<string name="display_presets">Design-Vorlagen</string>
|
||||||
|
<string name="animate">Animieren</string>
|
||||||
|
<string name="content_scale_fill_width">Breite ausfüllen</string>
|
||||||
|
<string name="content_scale_fill_height">Höhe ausfüllen</string>
|
||||||
|
<string name="display_preset_default">Wholphin Standard</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Kompakt</string>
|
||||||
|
<string name="volume_lowest">Niedrigste</string>
|
||||||
|
<string name="volume_low">Niedrig</string>
|
||||||
|
<string name="volume_medium">mittel</string>
|
||||||
|
<string name="volume_high">Hoch</string>
|
||||||
|
<string name="volume_full">Voll</string>
|
||||||
|
<string name="purple">Lila</string>
|
||||||
|
<string name="orange">Orange</string>
|
||||||
|
<string name="black">Schwarz</string>
|
||||||
|
<string name="backdrop_style_image">Nur Bild</string>
|
||||||
|
<string name="series_continueing">Aktuell</string>
|
||||||
|
<string name="in_app_screensaver">Eingebauten Bildschirmschoner nutzen</string>
|
||||||
|
<string name="delete_item">Bist du sicher, dass du diesen Eintrag löschen möchtest?</string>
|
||||||
|
<string name="actor">Schauspieler</string>
|
||||||
|
<string name="composer">Komponist</string>
|
||||||
|
<string name="writer">Autor</string>
|
||||||
|
<string name="guest_star">Nebenrolle</string>
|
||||||
|
<string name="producer">Produzent</string>
|
||||||
|
<string name="conductor">Dirigent</string>
|
||||||
|
<string name="lyricist">Liedtext-Schreiber</string>
|
||||||
|
<string name="artist">Künstler</string>
|
||||||
|
<string name="favorite_items">%s als Favorit speichern</string>
|
||||||
|
<string name="home_rows">Startseite - Reihen</string>
|
||||||
|
<string name="load_from_server">Vom Server Benutzerprofil laden</string>
|
||||||
|
<string name="save_to_server">Im Server Benutzerprofil speichern</string>
|
||||||
|
<string name="load_from_web_client">Vom Webclient laden</string>
|
||||||
|
<string name="increase_all_cards_size">Größe für alle Karten erhöhen</string>
|
||||||
|
<string name="use_thumb_images">Vorschaubilder benutzen</string>
|
||||||
|
<string name="no_subtitles_found">Es wurden keine Untertitel gefunden</string>
|
||||||
|
<string name="arranger">Arrangeur</string>
|
||||||
|
<string name="engineer">Ingenieur</string>
|
||||||
|
<string name="bold_blue">Dunkelblau</string>
|
||||||
|
<string name="image_subtitle_opacity">Untertitel-Transparenz</string>
|
||||||
|
<string name="image_type_thumb">Vorschaubild</string>
|
||||||
|
<string name="use_series">Serien Vorschaubilder verwenden</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[URL & API Key eingeben]]></string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[Untertitel suchen & herunterladen]]></string>
|
||||||
|
<string name="creator">Ersteller</string>
|
||||||
|
<string name="background_style_wrap">Gestreckt</string>
|
||||||
|
<string name="background_style_boxed">Zentriert</string>
|
||||||
|
<string name="mixer">Mischer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<string name="name">Nombre</string>
|
<string name="name">Nombre</string>
|
||||||
<string name="no_results">Sin resultados</string>
|
<string name="no_results">Sin resultados</string>
|
||||||
<string name="none">Ninguno</string>
|
<string name="none">Ninguno</string>
|
||||||
<string name="outro">Outro</string>
|
<string name="outro">Créditos finales</string>
|
||||||
<string name="people">Personas</string>
|
<string name="people">Personas</string>
|
||||||
<string name="play">Reproducir</string>
|
<string name="play">Reproducir</string>
|
||||||
<string name="playback">Reproducción</string>
|
<string name="playback">Reproducción</string>
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
<string name="create_playlist">Crear nueva lista de reproducción</string>
|
<string name="create_playlist">Crear nueva lista de reproducción</string>
|
||||||
<string name="add_to_playlist">Agregar a la lista de reproducción</string>
|
<string name="add_to_playlist">Agregar a la lista de reproducción</string>
|
||||||
<string name="one_click_pause">Pausar con un clic</string>
|
<string name="one_click_pause">Pausar con un clic</string>
|
||||||
<string name="one_click_pause_summary_on">Presiona el centro del D-Pad para pausar/reproducir</string>
|
<string name="one_click_pause_summary_on">Pulsa el botón central de la cruceta para reproducir/pausar</string>
|
||||||
<string name="success">Éxito</string>
|
<string name="success">Éxito</string>
|
||||||
<string name="runtime_sort">Duración</string>
|
<string name="runtime_sort">Duración</string>
|
||||||
<string name="extras">Extras</string>
|
<string name="extras">Extras</string>
|
||||||
|
|
@ -264,8 +264,8 @@
|
||||||
<string name="skip_back_preference">Retroceder</string>
|
<string name="skip_back_preference">Retroceder</string>
|
||||||
<string name="skip_commercials_behavior">Comportamiento al saltar anuncios</string>
|
<string name="skip_commercials_behavior">Comportamiento al saltar anuncios</string>
|
||||||
<string name="skip_forward_preference">Avanzar</string>
|
<string name="skip_forward_preference">Avanzar</string>
|
||||||
<string name="skip_intro_behavior">Comportamiento al saltar la intro</string>
|
<string name="skip_intro_behavior">Comportamiento al saltar la cabecera</string>
|
||||||
<string name="skip_outro_behavior">Comportamiento al saltar el outro</string>
|
<string name="skip_outro_behavior">Comportamiento al saltar créditos finales</string>
|
||||||
<string name="skip_previews_behavior">Comportamiento al saltar avances</string>
|
<string name="skip_previews_behavior">Comportamiento al saltar avances</string>
|
||||||
<string name="skip_recap_behavior">Comportamiento al saltar resumen</string>
|
<string name="skip_recap_behavior">Comportamiento al saltar resumen</string>
|
||||||
<string name="update_available">Actualización disponible</string>
|
<string name="update_available">Actualización disponible</string>
|
||||||
|
|
@ -291,8 +291,8 @@
|
||||||
<string name="skip_segment_commercial">Saltar anuncios</string>
|
<string name="skip_segment_commercial">Saltar anuncios</string>
|
||||||
<string name="skip_segment_preview">Saltar avance</string>
|
<string name="skip_segment_preview">Saltar avance</string>
|
||||||
<string name="skip_segment_recap">Saltar resumen</string>
|
<string name="skip_segment_recap">Saltar resumen</string>
|
||||||
<string name="skip_segment_outro">Saltar outro</string>
|
<string name="skip_segment_outro">Saltar créditos finales</string>
|
||||||
<string name="skip_segment_intro">Saltar intro</string>
|
<string name="skip_segment_intro">Saltar cabecera</string>
|
||||||
<string name="played">Reproducido</string>
|
<string name="played">Reproducido</string>
|
||||||
<string name="filter">FIltrar</string>
|
<string name="filter">FIltrar</string>
|
||||||
<string name="year">Año</string>
|
<string name="year">Año</string>
|
||||||
|
|
@ -401,6 +401,7 @@
|
||||||
<string name="clear_track_choices">Opciones de pista claras</string>
|
<string name="clear_track_choices">Opciones de pista claras</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -485,8 +486,8 @@
|
||||||
<string name="content_scale_fill">Rellenar</string>
|
<string name="content_scale_fill">Rellenar</string>
|
||||||
<string name="content_scale_fill_width">Ajustar al ancho</string>
|
<string name="content_scale_fill_width">Ajustar al ancho</string>
|
||||||
<string name="content_scale_fill_height">Ajustar al alto</string>
|
<string name="content_scale_fill_height">Ajustar al alto</string>
|
||||||
<string name="display_preset_series_thumb">Miniaturas de las series</string>
|
<string name="display_preset_series_thumb">Miniatura de la serie</string>
|
||||||
<string name="display_preset_episode_thumbnails">Miniaturas de los episodios</string>
|
<string name="display_preset_episode_thumbnails">Miniatura del episodio</string>
|
||||||
<string name="volume_lowest">Mínimo</string>
|
<string name="volume_lowest">Mínimo</string>
|
||||||
<string name="volume_low">Bajo</string>
|
<string name="volume_low">Bajo</string>
|
||||||
<string name="volume_medium">Medio</string>
|
<string name="volume_medium">Medio</string>
|
||||||
|
|
@ -534,4 +535,38 @@
|
||||||
<string name="search_and_download_subtitles"><![CDATA[Buscar y descargar subtítulos]]></string>
|
<string name="search_and_download_subtitles"><![CDATA[Buscar y descargar subtítulos]]></string>
|
||||||
<string name="no_subtitles_found">No se encontraron subtítulos externos</string>
|
<string name="no_subtitles_found">No se encontraron subtítulos externos</string>
|
||||||
<string name="series_continueing">Actualidad</string>
|
<string name="series_continueing">Actualidad</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s minuto</item>
|
||||||
|
<item quantity="many">%s minutos</item>
|
||||||
|
<item quantity="other">%s minutos</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Comenzar después de</string>
|
||||||
|
<string name="animate">Animación</string>
|
||||||
|
<string name="max_age_rating">Clasificación por edad máxima</string>
|
||||||
|
<string name="for_all_ages">Para todas las edades</string>
|
||||||
|
<string name="up_to_age">Menores de %s</string>
|
||||||
|
<string name="screensaver">Salvapantallas</string>
|
||||||
|
<string name="screensaver_settings">Configuración de salvapantallas</string>
|
||||||
|
<string name="start_screensaver">Iniciar salvapantallas</string>
|
||||||
|
<string name="duration">Duración</string>
|
||||||
|
<string name="photos">Fotos</string>
|
||||||
|
<string name="include_types">Incluir tipos</string>
|
||||||
|
<string name="include_types_summary">Qué tipos de elementos mostrar</string>
|
||||||
|
<string name="in_app_screensaver">Usar salvapantallas de la aplicación</string>
|
||||||
|
<string name="no_max">Sin límite</string>
|
||||||
|
<string name="actor">Intérprete</string>
|
||||||
|
<string name="composer">Compositor</string>
|
||||||
|
<string name="writer">Guionista</string>
|
||||||
|
<string name="guest_star">Estrella invitada</string>
|
||||||
|
<string name="producer">Productor</string>
|
||||||
|
<string name="conductor">Director musical</string>
|
||||||
|
<string name="lyricist">Letrista</string>
|
||||||
|
<string name="arranger">Arreglista</string>
|
||||||
|
<string name="engineer">Ingeniero de sonido</string>
|
||||||
|
<string name="mixer">Ingeniero de mezcla</string>
|
||||||
|
<string name="creator">Creador</string>
|
||||||
|
<string name="artist">Artista</string>
|
||||||
|
<string name="delete_item">¿Estás seguro de que quieres eliminar este elemento?</string>
|
||||||
|
<string name="show_media_management">Mostrar opciones de gestión de medios</string>
|
||||||
|
<string name="search_for">Buscar %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,7 @@
|
||||||
<string name="only_forced_subtitles">Vaid sundkorras kuvatud subtiitrid</string>
|
<string name="only_forced_subtitles">Vaid sundkorras kuvatud subtiitrid</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
|
||||||
|
|
@ -221,8 +221,8 @@
|
||||||
<string name="font_opacity">Opacité de la police</string>
|
<string name="font_opacity">Opacité de la police</string>
|
||||||
<string name="edge_style">Style des contours</string>
|
<string name="edge_style">Style des contours</string>
|
||||||
<string name="edge_color">Couleur des contours</string>
|
<string name="edge_color">Couleur des contours</string>
|
||||||
<string name="background_opacity">Opacité de l’arrière-plan</string>
|
<string name="background_opacity">Opacité de l\'arrière-plan</string>
|
||||||
<string name="background_style">Style de l’arrière-plan</string>
|
<string name="background_style">Style de l\'arrière-plan</string>
|
||||||
<string name="subtitle_style">Style des sous-titres</string>
|
<string name="subtitle_style">Style des sous-titres</string>
|
||||||
<string name="background_color">Couleur de fond</string>
|
<string name="background_color">Couleur de fond</string>
|
||||||
<string name="reset">Réinitialiser</string>
|
<string name="reset">Réinitialiser</string>
|
||||||
|
|
@ -234,10 +234,10 @@
|
||||||
<string name="exoplayer_options">Options ExoPlayer</string>
|
<string name="exoplayer_options">Options ExoPlayer</string>
|
||||||
<string name="skip_segment_unknown">Passer le segment</string>
|
<string name="skip_segment_unknown">Passer le segment</string>
|
||||||
<string name="skip_segment_commercial">Passer les publicités</string>
|
<string name="skip_segment_commercial">Passer les publicités</string>
|
||||||
<string name="skip_segment_preview">Passer l’aperçu</string>
|
<string name="skip_segment_preview">Passer l\'aperçu</string>
|
||||||
<string name="skip_segment_recap">Passer le récapitulatif</string>
|
<string name="skip_segment_recap">Passer le récapitulatif</string>
|
||||||
<string name="skip_segment_outro">Passer le générique de fin</string>
|
<string name="skip_segment_outro">Passer le générique de fin</string>
|
||||||
<string name="skip_segment_intro">Passer l’intro</string>
|
<string name="skip_segment_intro">Passer l\'intro</string>
|
||||||
<string name="played">Lu</string>
|
<string name="played">Lu</string>
|
||||||
<string name="filter">Filtre</string>
|
<string name="filter">Filtre</string>
|
||||||
<string name="year">Année</string>
|
<string name="year">Année</string>
|
||||||
|
|
@ -275,7 +275,7 @@
|
||||||
<string name="play_theme_music">Lire la musique de thème</string>
|
<string name="play_theme_music">Lire la musique de thème</string>
|
||||||
<string name="skip_back_on_resume_preference">Revenir en arrière à la reprise de la lecture</string>
|
<string name="skip_back_on_resume_preference">Revenir en arrière à la reprise de la lecture</string>
|
||||||
<string name="skip_commercials_behavior">Comportement de saut des publicités</string>
|
<string name="skip_commercials_behavior">Comportement de saut des publicités</string>
|
||||||
<string name="skip_intro_behavior">Comportement de saut de l’intro</string>
|
<string name="skip_intro_behavior">Comportement de saut de l\'intro</string>
|
||||||
<string name="skip_outro_behavior">Comportement de saut du générique de fin</string>
|
<string name="skip_outro_behavior">Comportement de saut du générique de fin</string>
|
||||||
<string name="skip_previews_behavior">Comportement de saut des aperçus</string>
|
<string name="skip_previews_behavior">Comportement de saut des aperçus</string>
|
||||||
<string name="skip_recap_behavior">Comportement de saut du récapitulatif</string>
|
<string name="skip_recap_behavior">Comportement de saut du récapitulatif</string>
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
<item quantity="many"/>
|
<item quantity="many"/>
|
||||||
<item quantity="other"/>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="combine_continue_next"><![CDATA[Combiner "Continuer à regarder" & "À suivre"]]></string>
|
<string name="combine_continue_next"><![CDATA[Combiner \"Continuer à regarder\" & \"À suivre\"]]></string>
|
||||||
<string name="download_and_update"><![CDATA[Télécharger & Mettre à jour]]></string>
|
<string name="download_and_update"><![CDATA[Télécharger & Mettre à jour]]></string>
|
||||||
<string name="search_and_download"><![CDATA[Rechercher & Télécharger]]></string>
|
<string name="search_and_download"><![CDATA[Rechercher & Télécharger]]></string>
|
||||||
<string name="tv_dvr_schedule">Calendrier du DVR</string>
|
<string name="tv_dvr_schedule">Calendrier du DVR</string>
|
||||||
|
|
@ -382,6 +382,7 @@
|
||||||
<string name="no_trailers">Pas de bandes-annonces</string>
|
<string name="no_trailers">Pas de bandes-annonces</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -480,4 +481,89 @@
|
||||||
<string name="display_presets_description">Préréglages intégrés pour styliser rapidement toutes les lignes</string>
|
<string name="display_presets_description">Préréglages intégrés pour styliser rapidement toutes les lignes</string>
|
||||||
<string name="customize_home_summary">Choisissez des lignes et des images sur la page d\'accueil</string>
|
<string name="customize_home_summary">Choisissez des lignes et des images sur la page d\'accueil</string>
|
||||||
<string name="favorite_items">Favori %s</string>
|
<string name="favorite_items">Favori %s</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s minute</item>
|
||||||
|
<item quantity="many">%s minutes</item>
|
||||||
|
<item quantity="other">%s minute</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Démarrer après</string>
|
||||||
|
<string name="animate">Animer</string>
|
||||||
|
<string name="max_age_rating">Classification par âge maximale</string>
|
||||||
|
<string name="no_max">Pas de maximum</string>
|
||||||
|
<string name="for_all_ages">Pour tous les âges</string>
|
||||||
|
<string name="up_to_age">Jusqu\'à l\'âge de %s</string>
|
||||||
|
<string name="screensaver">Écran de veille</string>
|
||||||
|
<string name="screensaver_settings">Paramètres de l\'économiseur d\'écran</string>
|
||||||
|
<string name="start_screensaver">Lancer l\'économiseur d\'écran</string>
|
||||||
|
<string name="duration">Durée</string>
|
||||||
|
<string name="photos">Photos</string>
|
||||||
|
<string name="include_types">Inclure les types</string>
|
||||||
|
<string name="include_types_summary">Quels types d\'éléments afficher des images pour</string>
|
||||||
|
<string name="content_scale_fit">Ajustement</string>
|
||||||
|
<string name="content_scale_crop">Recadrer</string>
|
||||||
|
<string name="content_scale_fill">Remplir</string>
|
||||||
|
<string name="content_scale_fill_width">Largeur de remplissage</string>
|
||||||
|
<string name="content_scale_fill_height">Hauteur de remplissage</string>
|
||||||
|
<string name="display_preset_default">Wholphin Par défaut</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Compact</string>
|
||||||
|
<string name="display_preset_series_thumb">Images miniatures de la série</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Images miniatures de l\'épisode</string>
|
||||||
|
<string name="volume_lowest">Le plus bas</string>
|
||||||
|
<string name="volume_low">Faible</string>
|
||||||
|
<string name="volume_medium">Moyen</string>
|
||||||
|
<string name="volume_high">Haute</string>
|
||||||
|
<string name="volume_full">Complet</string>
|
||||||
|
<string name="purple">Violet</string>
|
||||||
|
<string name="orange">Orange</string>
|
||||||
|
<string name="bold_blue">Bleu gras</string>
|
||||||
|
<string name="black">Noir</string>
|
||||||
|
<string name="skip_ignore">Ignorer</string>
|
||||||
|
<string name="skip_automatically">Passer automatiquement</string>
|
||||||
|
<string name="skip_ask">Demander à passer</string>
|
||||||
|
<string name="ffmpeg_fallback">N\'utilisez FFmpeg que si aucun décodeur intégré n\'existe</string>
|
||||||
|
<string name="ffmpeg_prefer">Préférez utiliser FFmpeg plutôt que les décodeurs intégrés</string>
|
||||||
|
<string name="ffmpeg_never">N\'utilisez jamais les décodeurs FFmpeg</string>
|
||||||
|
<string name="next_up_playback_end">À la fin de la lecture</string>
|
||||||
|
<string name="next_up_outro">Pendant le générique de fin/outro</string>
|
||||||
|
<string name="white">Blanc</string>
|
||||||
|
<string name="light_gray">Gris clair</string>
|
||||||
|
<string name="dark_gray">Gris foncé</string>
|
||||||
|
<string name="yellow">Jaune</string>
|
||||||
|
<string name="cyan">Cyan</string>
|
||||||
|
<string name="magenta">Magenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Contour</string>
|
||||||
|
<string name="subtitle_edge_shadow">Ombre</string>
|
||||||
|
<string name="background_style_wrap">Envelopper</string>
|
||||||
|
<string name="background_style_boxed">Encadré</string>
|
||||||
|
<string name="prefer_mpv">Préférer MPV</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">Utiliser ExoPlayer pour la lecture HDR</string>
|
||||||
|
<string name="aspect_ratios_poster">Affiche (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Carré (1:1)</string>
|
||||||
|
<string name="image_type_primary">Primaire</string>
|
||||||
|
<string name="image_type_thumb">Miniature</string>
|
||||||
|
<string name="backdrop_style_dynamic">Image avec couleur dynamique</string>
|
||||||
|
<string name="backdrop_style_image">Image uniquement</string>
|
||||||
|
<string name="enter_url_api_key">Entrez l\'URL et la clé API</string>
|
||||||
|
<string name="api_key">Clé API</string>
|
||||||
|
<string name="seerr_login">Se connecter au serveur Seerr</string>
|
||||||
|
<string name="seerr_jellyfin_user">Utilisateur Jellyfin</string>
|
||||||
|
<string name="seerr_local_user">Utilisateur local</string>
|
||||||
|
<string name="search_and_download_subtitles">Rechercher et télécharger des sous-titres</string>
|
||||||
|
<string name="no_subtitles_found">Aucun sous-titre distant n\'a été trouvé</string>
|
||||||
|
<string name="series_continueing">Présent</string>
|
||||||
|
<string name="in_app_screensaver">Utiliser l\'économiseur d\'écran intégré</string>
|
||||||
|
<string name="actor">Acteur</string>
|
||||||
|
<string name="composer">Compositeur</string>
|
||||||
|
<string name="writer">Écrivain</string>
|
||||||
|
<string name="guest_star">Invité spécial</string>
|
||||||
|
<string name="producer">Producteur</string>
|
||||||
|
<string name="conductor">Conducteur</string>
|
||||||
|
<string name="lyricist">Parolier</string>
|
||||||
|
<string name="arranger">Arrangeur</string>
|
||||||
|
<string name="engineer">Ingénieur</string>
|
||||||
|
<string name="mixer">Mélangeur</string>
|
||||||
|
<string name="creator">Créateur</string>
|
||||||
|
<string name="artist">Artiste</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -242,8 +242,8 @@
|
||||||
<string name="font_size">Ukuran font</string>
|
<string name="font_size">Ukuran font</string>
|
||||||
<string name="font_color">Warna font</string>
|
<string name="font_color">Warna font</string>
|
||||||
<string name="font_opacity">Transparansi font</string>
|
<string name="font_opacity">Transparansi font</string>
|
||||||
<string name="edge_style">Jenis garis tepi</string>
|
<string name="edge_style">Jenis pinggiran</string>
|
||||||
<string name="edge_color">Warna garis tepi</string>
|
<string name="edge_color">Warna pinggiran</string>
|
||||||
<string name="background_opacity">Transparansi latar belakang</string>
|
<string name="background_opacity">Transparansi latar belakang</string>
|
||||||
<string name="background_style">Gaya latar belakang</string>
|
<string name="background_style">Gaya latar belakang</string>
|
||||||
<string name="subtitle_style">Gaya subtitel</string>
|
<string name="subtitle_style">Gaya subtitel</string>
|
||||||
|
|
@ -318,7 +318,7 @@
|
||||||
<string name="require_pin_code">Membutuhkan PIN untuk profil</string>
|
<string name="require_pin_code">Membutuhkan PIN untuk profil</string>
|
||||||
<string name="will_remove_pin">PIN akan dihapus</string>
|
<string name="will_remove_pin">PIN akan dihapus</string>
|
||||||
<string name="view_options">Opsi tampilan</string>
|
<string name="view_options">Opsi tampilan</string>
|
||||||
<string name="edge_size">Ukuran garis tepi</string>
|
<string name="edge_size">Ukuran pinggiran</string>
|
||||||
<string name="automatic">Otomatis</string>
|
<string name="automatic">Otomatis</string>
|
||||||
<string name="username_or_password">Gunakan nama pengguna/kata sandi</string>
|
<string name="username_or_password">Gunakan nama pengguna/kata sandi</string>
|
||||||
<string name="subtitle_margin">Batas tepi</string>
|
<string name="subtitle_margin">Batas tepi</string>
|
||||||
|
|
@ -369,6 +369,7 @@
|
||||||
<string name="clear_track_choices">Hapus pilihan trek</string>
|
<string name="clear_track_choices">Hapus pilihan trek</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -446,4 +447,79 @@
|
||||||
<string name="display_presets_description">Preset bawaan untuk kustomisasi cepat semua baris</string>
|
<string name="display_presets_description">Preset bawaan untuk kustomisasi cepat semua baris</string>
|
||||||
<string name="customize_home_summary">Pilih baris dan gambar di halaman beranda</string>
|
<string name="customize_home_summary">Pilih baris dan gambar di halaman beranda</string>
|
||||||
<string name="favorite_items">Favoritkan %s</string>
|
<string name="favorite_items">Favoritkan %s</string>
|
||||||
|
<string name="artist">Artis</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="other">%s menit</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Mulai setelah</string>
|
||||||
|
<string name="animate">Animasi</string>
|
||||||
|
<string name="max_age_rating">Rating usia maks</string>
|
||||||
|
<string name="no_max">Tidak ada maks</string>
|
||||||
|
<string name="for_all_ages">Untuk semua umur</string>
|
||||||
|
<string name="up_to_age">Maks. usia %s</string>
|
||||||
|
<string name="screensaver">Screensaver</string>
|
||||||
|
<string name="screensaver_settings">Pengaturan screensaver</string>
|
||||||
|
<string name="start_screensaver">Mulai screensaver</string>
|
||||||
|
<string name="duration">Durasi</string>
|
||||||
|
<string name="photos">Foto</string>
|
||||||
|
<string name="include_types">Sertakan jenis</string>
|
||||||
|
<string name="include_types_summary">Tampilkan gambar untuk jenis item</string>
|
||||||
|
<string name="display_preset_default">Wholphin Bawaan</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Ringkas</string>
|
||||||
|
<string name="display_preset_series_thumb">Gambar Thumbnail Serial</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Gambar Thumbnail Episode</string>
|
||||||
|
<string name="volume_lowest">Terendah</string>
|
||||||
|
<string name="volume_low">Rendah</string>
|
||||||
|
<string name="volume_medium">Sedang</string>
|
||||||
|
<string name="volume_high">Tinggi</string>
|
||||||
|
<string name="volume_full">Penuh</string>
|
||||||
|
<string name="purple">Ungu</string>
|
||||||
|
<string name="orange">Oranye</string>
|
||||||
|
<string name="bold_blue">Biru Tegas</string>
|
||||||
|
<string name="black">Hitam</string>
|
||||||
|
<string name="skip_ignore">Abaikan</string>
|
||||||
|
<string name="skip_automatically">Lewati otomatis</string>
|
||||||
|
<string name="skip_ask">Tanya sebelum melewati</string>
|
||||||
|
<string name="ffmpeg_fallback">Gunakan FFmpeg hanya jika dekoder bawaan tidak ada</string>
|
||||||
|
<string name="ffmpeg_prefer">Utamakan FFmpeg daripada dekoder bawaan</string>
|
||||||
|
<string name="ffmpeg_never">Jangan pernah gunakan dekoder FFmpeg</string>
|
||||||
|
<string name="next_up_playback_end">Di akhir pemutaran</string>
|
||||||
|
<string name="next_up_outro">Selama kredit akhir/outro</string>
|
||||||
|
<string name="white">Putih</string>
|
||||||
|
<string name="light_gray">Abu-abu muda</string>
|
||||||
|
<string name="dark_gray">Abu-abu tua</string>
|
||||||
|
<string name="yellow">Kuning</string>
|
||||||
|
<string name="cyan">Sian</string>
|
||||||
|
<string name="magenta">Magenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Garis luar</string>
|
||||||
|
<string name="prefer_mpv">Utamakan MPV</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">Gunakan ExoPlayer untuk pemutaran HDR</string>
|
||||||
|
<string name="aspect_ratios_poster">Poster (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Persegi (1:1)</string>
|
||||||
|
<string name="image_type_primary">Utama</string>
|
||||||
|
<string name="image_type_thumb">Thumbnail</string>
|
||||||
|
<string name="backdrop_style_dynamic">Gambar dengan warna dinamis</string>
|
||||||
|
<string name="backdrop_style_image">Hanya gambar</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[Enter URL & API Key]]></string>
|
||||||
|
<string name="api_key">API Key</string>
|
||||||
|
<string name="seerr_login">Masuk ke server Seerr</string>
|
||||||
|
<string name="seerr_jellyfin_user">Pengguna Jellyfin</string>
|
||||||
|
<string name="seerr_local_user">Pengguna lokal</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[Search & download subtitles]]></string>
|
||||||
|
<string name="no_subtitles_found">Subtitel remote tidak ditemukan</string>
|
||||||
|
<string name="series_continueing">Tayang</string>
|
||||||
|
<string name="in_app_screensaver">Gunakan screensaver dalam aplikasi</string>
|
||||||
|
<string name="actor">Aktor</string>
|
||||||
|
<string name="composer">Komposer</string>
|
||||||
|
<string name="writer">Penulis</string>
|
||||||
|
<string name="guest_star">Bintang tamu</string>
|
||||||
|
<string name="producer">Produser</string>
|
||||||
|
<string name="conductor">Konduktor</string>
|
||||||
|
<string name="lyricist">Penulis lirik</string>
|
||||||
|
<string name="arranger">Aransemen</string>
|
||||||
|
<string name="engineer">Teknisi</string>
|
||||||
|
<string name="mixer">Mixer</string>
|
||||||
|
<string name="creator">Kreator</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,7 @@
|
||||||
<string name="only_forced_subtitles">Solo sottotitoli forzati</string>
|
<string name="only_forced_subtitles">Solo sottotitoli forzati</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -480,4 +481,92 @@
|
||||||
<string name="use_series">Usa immagine delle serie</string>
|
<string name="use_series">Usa immagine delle serie</string>
|
||||||
<string name="customize_home_summary">Scegli righe e immagini nella home page</string>
|
<string name="customize_home_summary">Scegli righe e immagini nella home page</string>
|
||||||
<string name="favorite_items">Preferito %s</string>
|
<string name="favorite_items">Preferito %s</string>
|
||||||
|
<string name="content_scale_fit">Adatta</string>
|
||||||
|
<string name="content_scale_crop">Ritaglia</string>
|
||||||
|
<string name="content_scale_fill">Riempi</string>
|
||||||
|
<string name="content_scale_fill_width">Riempi larghezza</string>
|
||||||
|
<string name="content_scale_fill_height">Riempi altezza</string>
|
||||||
|
<string name="display_preset_default">Wholphin predefinito</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Compatto</string>
|
||||||
|
<string name="display_preset_series_thumb">Immagini miniatura serie</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Immagini miniatura episodio</string>
|
||||||
|
<string name="volume_lowest">Minimo</string>
|
||||||
|
<string name="volume_low">Basso</string>
|
||||||
|
<string name="volume_medium">Medio</string>
|
||||||
|
<string name="volume_high">Alto</string>
|
||||||
|
<string name="volume_full">Massimo</string>
|
||||||
|
<string name="purple">Viola</string>
|
||||||
|
<string name="orange">Arancione</string>
|
||||||
|
<string name="bold_blue">Blu intenso</string>
|
||||||
|
<string name="black">Nero</string>
|
||||||
|
<string name="skip_ignore">Ignora</string>
|
||||||
|
<string name="skip_automatically">Salta automaticamente</string>
|
||||||
|
<string name="skip_ask">Chiedi se saltare</string>
|
||||||
|
<string name="ffmpeg_fallback">Usa FFmpeg solo se non esiste un decodificatore integrato</string>
|
||||||
|
<string name="ffmpeg_prefer">Preferisci FFmpeg ai decoder integrati</string>
|
||||||
|
<string name="ffmpeg_never">Non usare mai i decoder FFmpeg</string>
|
||||||
|
<string name="next_up_playback_end">Alla fine della riproduzione</string>
|
||||||
|
<string name="next_up_outro">Durante i titoli di coda</string>
|
||||||
|
<string name="white">Bianco</string>
|
||||||
|
<string name="light_gray">Grigio chiaro</string>
|
||||||
|
<string name="dark_gray">Grigio scuro</string>
|
||||||
|
<string name="yellow">Giallo</string>
|
||||||
|
<string name="cyan">Ciano</string>
|
||||||
|
<string name="magenta">Magenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Contorno</string>
|
||||||
|
<string name="subtitle_edge_shadow">Ombra</string>
|
||||||
|
<string name="background_style_wrap">Avvolgi</string>
|
||||||
|
<string name="background_style_boxed">Con riquadro</string>
|
||||||
|
<string name="prefer_mpv">Preferisci MVP</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">Usa ExoPlayer per la riproduzione HDR</string>
|
||||||
|
<string name="aspect_ratios_poster">Copertina (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Quadrato (1:1)</string>
|
||||||
|
<string name="image_type_primary">Principale</string>
|
||||||
|
<string name="image_type_thumb">Miniatura</string>
|
||||||
|
<string name="backdrop_style_dynamic">Immagine con colore dinamico</string>
|
||||||
|
<string name="backdrop_style_image">Solo immagine</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[Inserisci URL e chiave API]]></string>
|
||||||
|
<string name="api_key">Chiave API</string>
|
||||||
|
<string name="seerr_login">Accedi al server Seerr</string>
|
||||||
|
<string name="seerr_jellyfin_user">Utente Jellyfin</string>
|
||||||
|
<string name="seerr_local_user">Utente locale</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[Cerca e scarica sottotitoli]]></string>
|
||||||
|
<string name="no_subtitles_found">Nessun sottotitolo remoto trovato</string>
|
||||||
|
<string name="series_continueing">In corso</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s minuto</item>
|
||||||
|
<item quantity="many">%s minuti</item>
|
||||||
|
<item quantity="other">%s minuti</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Inizia dopo</string>
|
||||||
|
<string name="animate">Anima</string>
|
||||||
|
<string name="max_age_rating">Età massima</string>
|
||||||
|
<string name="for_all_ages">Per tutte le età</string>
|
||||||
|
<string name="up_to_age">Fino a %s anni</string>
|
||||||
|
<string name="screensaver">Salvaschermo</string>
|
||||||
|
<string name="screensaver_settings">Impostazioni salvaschermo</string>
|
||||||
|
<string name="start_screensaver">Avvia salvaschermo</string>
|
||||||
|
<string name="duration">Durata</string>
|
||||||
|
<string name="photos">Foto</string>
|
||||||
|
<string name="include_types">Includi tipi</string>
|
||||||
|
<string name="include_types_summary">Quali tipi di elementi mostrare nelle immagini</string>
|
||||||
|
<string name="no_max">Nessun massimo</string>
|
||||||
|
<string name="in_app_screensaver">Usa salvaschermo dell\'app</string>
|
||||||
|
<string name="composer">Compositore</string>
|
||||||
|
<string name="writer">Sceneggiatore</string>
|
||||||
|
<string name="actor">Attore</string>
|
||||||
|
<string name="producer">Produttore</string>
|
||||||
|
<string name="guest_star">Ospite speciale</string>
|
||||||
|
<string name="conductor">Direttore d’orchestra</string>
|
||||||
|
<string name="lyricist">Paroliere</string>
|
||||||
|
<string name="arranger">Arrangiatore</string>
|
||||||
|
<string name="engineer">Ingegnere del suono</string>
|
||||||
|
<string name="mixer">Tecnico di mixaggio</string>
|
||||||
|
<string name="artist">Artista</string>
|
||||||
|
<string name="creator">Creatore</string>
|
||||||
|
<string name="search_for">Cerca %s</string>
|
||||||
|
<string name="delete_item">Sei sicuro di voler cancellare questo elemento?</string>
|
||||||
|
<string name="show_media_management">Mostra opzioni di gestione dei media</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@
|
||||||
<string name="more">Mais</string>
|
<string name="more">Mais</string>
|
||||||
<plurals name="movies">
|
<plurals name="movies">
|
||||||
<item quantity="one">Filmes</item>
|
<item quantity="one">Filmes</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="name">Nome</string>
|
<string name="name">Nome</string>
|
||||||
<string name="next_up">A Seguir</string>
|
<string name="next_up">A Seguir</string>
|
||||||
|
|
@ -81,8 +81,8 @@
|
||||||
<string name="path">Caminho</string>
|
<string name="path">Caminho</string>
|
||||||
<plurals name="people">
|
<plurals name="people">
|
||||||
<item quantity="one">Pessoas</item>
|
<item quantity="one">Pessoas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="play_count">Contagem de Reproduções</string>
|
<string name="play_count">Contagem de Reproduções</string>
|
||||||
<string name="play_from_here">Reproduzir a partir daqui</string>
|
<string name="play_from_here">Reproduzir a partir daqui</string>
|
||||||
|
|
@ -152,8 +152,8 @@
|
||||||
<string name="trailer">Trailer</string>
|
<string name="trailer">Trailer</string>
|
||||||
<plurals name="trailers">
|
<plurals name="trailers">
|
||||||
<item quantity="one">Trailers</item>
|
<item quantity="one">Trailers</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="tv_dvr_schedule">Agenda do DVR</string>
|
<string name="tv_dvr_schedule">Agenda do DVR</string>
|
||||||
<string name="tv_guide">Guia</string>
|
<string name="tv_guide">Guia</string>
|
||||||
|
|
@ -161,8 +161,8 @@
|
||||||
<string name="tv_seasons">Temporadas</string>
|
<string name="tv_seasons">Temporadas</string>
|
||||||
<plurals name="tv_shows">
|
<plurals name="tv_shows">
|
||||||
<item quantity="one">Programas de TV</item>
|
<item quantity="one">Programas de TV</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="ui_interface">Interface</string>
|
<string name="ui_interface">Interface</string>
|
||||||
<string name="unknown">Desconhecido</string>
|
<string name="unknown">Desconhecido</string>
|
||||||
|
|
@ -190,53 +190,53 @@
|
||||||
<string name="extras">Extras</string>
|
<string name="extras">Extras</string>
|
||||||
<plurals name="other_extras">
|
<plurals name="other_extras">
|
||||||
<item quantity="one">Outro</item>
|
<item quantity="one">Outro</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="behind_the_scenes">
|
<plurals name="behind_the_scenes">
|
||||||
<item quantity="one">Por Trás das Cenas</item>
|
<item quantity="one">Por Trás das Cenas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="theme_songs">
|
<plurals name="theme_songs">
|
||||||
<item quantity="one">Músicas Tema</item>
|
<item quantity="one">Músicas Tema</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="theme_videos">
|
<plurals name="theme_videos">
|
||||||
<item quantity="one">Vídeos Tema</item>
|
<item quantity="one">Vídeos Tema</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="clips">
|
<plurals name="clips">
|
||||||
<item quantity="one">Clipes</item>
|
<item quantity="one">Clipes</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="deleted_scenes">
|
<plurals name="deleted_scenes">
|
||||||
<item quantity="one">Cenas Deletadas</item>
|
<item quantity="one">Cenas Deletadas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="interviews">
|
<plurals name="interviews">
|
||||||
<item quantity="one">Entrevistas</item>
|
<item quantity="one">Entrevistas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="scenes">
|
<plurals name="scenes">
|
||||||
<item quantity="one">Cenas</item>
|
<item quantity="one">Cenas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="samples">
|
<plurals name="samples">
|
||||||
<item quantity="one">Amostras</item>
|
<item quantity="one">Amostras</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="shorts">
|
<plurals name="shorts">
|
||||||
<item quantity="one">Curtas</item>
|
<item quantity="one">Curtas</item>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="favorite_items">Favoritar %s</string>
|
<string name="favorite_items">Favoritar %s</string>
|
||||||
<plurals name="downloads">
|
<plurals name="downloads">
|
||||||
|
|
@ -405,6 +405,7 @@
|
||||||
<string name="clear_track_choices">Limpar escolhas de faixas</string>
|
<string name="clear_track_choices">Limpar escolhas de faixas</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
<string name="top_unwatched">Mais Votados Não Assistidos</string>
|
<string name="top_unwatched">Mais Votados Não Assistidos</string>
|
||||||
<string name="trailer">Trailer</string>
|
<string name="trailer">Trailer</string>
|
||||||
<plurals name="trailers">
|
<plurals name="trailers">
|
||||||
<item quantity="one">Trailers</item>
|
<item quantity="one">Trailer</item>
|
||||||
<item quantity="many">Trailers</item>
|
<item quantity="many">Trailers</item>
|
||||||
<item quantity="other">Trailers</item>
|
<item quantity="other">Trailers</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
@ -382,6 +382,7 @@
|
||||||
<string name="clear_track_choices">Apagar seleções de faixas</string>
|
<string name="clear_track_choices">Apagar seleções de faixas</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -463,8 +464,8 @@
|
||||||
<string name="apply_all_rows">Aplicar a todas as linhas</string>
|
<string name="apply_all_rows">Aplicar a todas as linhas</string>
|
||||||
<string name="customize_home">Personalizar página inicial</string>
|
<string name="customize_home">Personalizar página inicial</string>
|
||||||
<string name="home_rows">Linhas da página inicial</string>
|
<string name="home_rows">Linhas da página inicial</string>
|
||||||
<string name="load_from_server">Carregar do servidor</string>
|
<string name="load_from_server">Carregar do perfil de utilizador do servidor</string>
|
||||||
<string name="save_to_server">Gravar para o servidor</string>
|
<string name="save_to_server">Guardar no perfil de utilizador do servidor</string>
|
||||||
<string name="load_from_web_client">Carregar a partir do cliente web</string>
|
<string name="load_from_web_client">Carregar a partir do cliente web</string>
|
||||||
<string name="use_series">Usar imagem da série</string>
|
<string name="use_series">Usar imagem da série</string>
|
||||||
<string name="add_row_for">Adicionar linha para %1$s</string>
|
<string name="add_row_for">Adicionar linha para %1$s</string>
|
||||||
|
|
@ -479,4 +480,90 @@
|
||||||
<string name="display_presets_description">Predefinições embarcadas para definir todas as linhas rapidamente</string>
|
<string name="display_presets_description">Predefinições embarcadas para definir todas as linhas rapidamente</string>
|
||||||
<string name="customize_home_summary">Escolha as linhas e imagens na página inicial</string>
|
<string name="customize_home_summary">Escolha as linhas e imagens na página inicial</string>
|
||||||
<string name="decrease_all_cards_size">Diminuir o tamanho de todos os cartões</string>
|
<string name="decrease_all_cards_size">Diminuir o tamanho de todos os cartões</string>
|
||||||
|
<string name="favorite_items">Favoritos %s</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s minuto</item>
|
||||||
|
<item quantity="many">%s minutos</item>
|
||||||
|
<item quantity="other">%s minutos</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Começar depois</string>
|
||||||
|
<string name="animate">Animado</string>
|
||||||
|
<string name="max_age_rating">Idade máxima</string>
|
||||||
|
<string name="no_max">Sem máximo</string>
|
||||||
|
<string name="for_all_ages">Para todas as idades</string>
|
||||||
|
<string name="up_to_age">Até a idade %s</string>
|
||||||
|
<string name="screensaver">Proteção de ecrã</string>
|
||||||
|
<string name="screensaver_settings">Definições de protecção de ecrã</string>
|
||||||
|
<string name="start_screensaver">Iniciar protecção de ecrã</string>
|
||||||
|
<string name="duration">Duração</string>
|
||||||
|
<string name="photos">Fotos</string>
|
||||||
|
<string name="include_types">Incluir tipos</string>
|
||||||
|
<string name="include_types_summary">Que tipos de itens devem mostrar imagens</string>
|
||||||
|
<string name="content_scale_fit">Ajustar</string>
|
||||||
|
<string name="content_scale_crop">Recortar</string>
|
||||||
|
<string name="content_scale_fill">Preencher</string>
|
||||||
|
<string name="content_scale_fill_width">Preencher largura</string>
|
||||||
|
<string name="content_scale_fill_height">Preencher altura</string>
|
||||||
|
<string name="display_preset_default">Padrão do Wholphin</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Compacto</string>
|
||||||
|
<string name="display_preset_series_thumb">Imagens em miniatura da série</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Imagens em miniatura do episódio</string>
|
||||||
|
<string name="volume_lowest">Mínimo</string>
|
||||||
|
<string name="volume_low">Baixo</string>
|
||||||
|
<string name="volume_medium">Médio</string>
|
||||||
|
<string name="volume_high">Alto</string>
|
||||||
|
<string name="volume_full">Máximo</string>
|
||||||
|
<string name="purple">Roxo</string>
|
||||||
|
<string name="orange">Laranja</string>
|
||||||
|
<string name="bold_blue">Azul intenso</string>
|
||||||
|
<string name="black">Preto</string>
|
||||||
|
<string name="skip_ignore">Ignorar</string>
|
||||||
|
<string name="skip_automatically">Saltar automaticamente</string>
|
||||||
|
<string name="skip_ask">Pedir para saltar</string>
|
||||||
|
<string name="ffmpeg_fallback">Usa FFmpeg apenas se não houver um descodificador integrado</string>
|
||||||
|
<string name="ffmpeg_prefer">Prefere o FFmpeg aos descodificadores integrados</string>
|
||||||
|
<string name="ffmpeg_never">Nunca usar os descodificadores FFmpeg</string>
|
||||||
|
<string name="next_up_playback_end">No final da reprodução</string>
|
||||||
|
<string name="next_up_outro">Durante os créditos finais</string>
|
||||||
|
<string name="white">Branco</string>
|
||||||
|
<string name="light_gray">Cinzento claro</string>
|
||||||
|
<string name="dark_gray">Cinzento escuro</string>
|
||||||
|
<string name="yellow">Amarelo</string>
|
||||||
|
<string name="cyan">Ciano</string>
|
||||||
|
<string name="magenta">Magenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Contorno</string>
|
||||||
|
<string name="subtitle_edge_shadow">Sombra</string>
|
||||||
|
<string name="background_style_wrap">Envolver</string>
|
||||||
|
<string name="background_style_boxed">Com caixa</string>
|
||||||
|
<string name="prefer_mpv">Preferir MPV</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">Usar ExoPlayer para reprodução HDR</string>
|
||||||
|
<string name="aspect_ratios_poster">Poster (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Quadrado (1:1)</string>
|
||||||
|
<string name="image_type_primary">Primária</string>
|
||||||
|
<string name="image_type_thumb">Miniatura</string>
|
||||||
|
<string name="backdrop_style_dynamic">Imagem com cor dinâmica</string>
|
||||||
|
<string name="backdrop_style_image">Apenas imagem</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[Inserir URL e Chave API]]></string>
|
||||||
|
<string name="api_key">Chave API</string>
|
||||||
|
<string name="seerr_login">Iniciar sessão no servidor Seerr</string>
|
||||||
|
<string name="seerr_jellyfin_user">Utilizador Jellyfin</string>
|
||||||
|
<string name="seerr_local_user">Utilizador local</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[Procurar e transferir legendas]]></string>
|
||||||
|
<string name="no_subtitles_found">Não foram encontradas legendas remotas</string>
|
||||||
|
<string name="series_continueing">Em curso</string>
|
||||||
|
<string name="in_app_screensaver">Usar protetor de ecrã da aplicação</string>
|
||||||
|
<string name="actor">Ator</string>
|
||||||
|
<string name="composer">Compositor</string>
|
||||||
|
<string name="writer">Argumentista</string>
|
||||||
|
<string name="guest_star">Convidado especial</string>
|
||||||
|
<string name="producer">Produtor</string>
|
||||||
|
<string name="conductor">Director musicar</string>
|
||||||
|
<string name="lyricist">Letrista</string>
|
||||||
|
<string name="arranger">Arranjador</string>
|
||||||
|
<string name="engineer">Engenheiro de som</string>
|
||||||
|
<string name="mixer">Técnico de mixagem</string>
|
||||||
|
<string name="creator">Criador</string>
|
||||||
|
<string name="artist">Artista</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,9 @@
|
||||||
<string name="trailer">Trailer</string>
|
<string name="trailer">Trailer</string>
|
||||||
<plurals name="trailers">
|
<plurals name="trailers">
|
||||||
<item quantity="one">Trailery</item>
|
<item quantity="one">Trailery</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="tv_dvr_schedule">DVR rozvrh</string>
|
<string name="tv_dvr_schedule">DVR rozvrh</string>
|
||||||
<string name="tv_guide">Sprievodca</string>
|
<string name="tv_guide">Sprievodca</string>
|
||||||
|
|
@ -156,9 +156,9 @@
|
||||||
<string name="tv_seasons">Série</string>
|
<string name="tv_seasons">Série</string>
|
||||||
<plurals name="tv_shows">
|
<plurals name="tv_shows">
|
||||||
<item quantity="one">Seriály</item>
|
<item quantity="one">Seriály</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="ui_interface">Rozhranie</string>
|
<string name="ui_interface">Rozhranie</string>
|
||||||
<string name="unknown">Neznámy</string>
|
<string name="unknown">Neznámy</string>
|
||||||
|
|
@ -186,69 +186,69 @@
|
||||||
<string name="extras">Bonusový materiál</string>
|
<string name="extras">Bonusový materiál</string>
|
||||||
<plurals name="other_extras">
|
<plurals name="other_extras">
|
||||||
<item quantity="one">Ostatné</item>
|
<item quantity="one">Ostatné</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="behind_the_scenes">
|
<plurals name="behind_the_scenes">
|
||||||
<item quantity="one">Zo zákulisia</item>
|
<item quantity="one">Zo zákulisia</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="theme_songs">
|
<plurals name="theme_songs">
|
||||||
<item quantity="one">Tématické piesne</item>
|
<item quantity="one">Tématické piesne</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="theme_videos">
|
<plurals name="theme_videos">
|
||||||
<item quantity="one">Tématické videá</item>
|
<item quantity="one">Tématické videá</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="clips">
|
<plurals name="clips">
|
||||||
<item quantity="one">Klipy</item>
|
<item quantity="one">Klipy</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="deleted_scenes">
|
<plurals name="deleted_scenes">
|
||||||
<item quantity="one">Vymazané scény</item>
|
<item quantity="one">Vymazané scény</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="interviews">
|
<plurals name="interviews">
|
||||||
<item quantity="one">Rozhovory</item>
|
<item quantity="one">Rozhovory</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="scenes">
|
<plurals name="scenes">
|
||||||
<item quantity="one">Scény</item>
|
<item quantity="one">Scény</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="samples">
|
<plurals name="samples">
|
||||||
<item quantity="one">Vzorky</item>
|
<item quantity="one">Vzorky</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="featurettes">
|
<plurals name="featurettes">
|
||||||
<item quantity="one">Krátkometrážne filmy</item>
|
<item quantity="one">Krátkometrážne filmy</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="shorts">
|
<plurals name="shorts">
|
||||||
<item quantity="one">Krátke videá</item>
|
<item quantity="one">Krátke videá</item>
|
||||||
<item quantity="few"></item>
|
<item quantity="few"/>
|
||||||
<item quantity="many"></item>
|
<item quantity="many"/>
|
||||||
<item quantity="other"></item>
|
<item quantity="other"/>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="favorite_items">Obľúbené %s</string>
|
<string name="favorite_items">Obľúbené %s</string>
|
||||||
<plurals name="downloads">
|
<plurals name="downloads">
|
||||||
|
|
|
||||||
|
|
@ -277,4 +277,49 @@
|
||||||
<string name="enter_pin">Skriv in PIN</string>
|
<string name="enter_pin">Skriv in PIN</string>
|
||||||
<string name="sign_in_auto">Logga in automatiskt</string>
|
<string name="sign_in_auto">Logga in automatiskt</string>
|
||||||
<string name="use_server_credentials">Logga in via server</string>
|
<string name="use_server_credentials">Logga in via server</string>
|
||||||
|
<string name="enter_server_address">Ange server-adress</string>
|
||||||
|
<string name="voice_error_network">Nätverksfel</string>
|
||||||
|
<string name="voice_error_unknown">Okänt fel</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s minut</item>
|
||||||
|
<item quantity="other">%s minuter</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="bold_font">Fet stil</string>
|
||||||
|
<string name="dolby_atmos">Dolby Atmos</string>
|
||||||
|
<string name="discard_change">Avbryt ändringar?</string>
|
||||||
|
<string name="require_pin_code">Kräv PIN för profilen</string>
|
||||||
|
<string name="resolution">Upplösning</string>
|
||||||
|
<string name="language">Språk</string>
|
||||||
|
<string name="yes">Ja</string>
|
||||||
|
<string name="no">Nej</string>
|
||||||
|
<string name="live_tv_repeat">Upprepa</string>
|
||||||
|
<string name="password">Lösenord</string>
|
||||||
|
<string name="username">Användarnamn</string>
|
||||||
|
<string name="creator">Skapare</string>
|
||||||
|
<string name="engineer">Ingenjör</string>
|
||||||
|
<string name="actor">Skådespelare</string>
|
||||||
|
<string name="composer">Kompositör</string>
|
||||||
|
<string name="writer">Författare</string>
|
||||||
|
<string name="producer">Producent</string>
|
||||||
|
<string name="backdrop_style_image">Endast bild</string>
|
||||||
|
<string name="white">Vit</string>
|
||||||
|
<string name="light_gray">Ljusgrå</string>
|
||||||
|
<string name="dark_gray">Mörkgrå</string>
|
||||||
|
<string name="yellow">Gul</string>
|
||||||
|
<string name="purple">Lila</string>
|
||||||
|
<string name="black">Svart</string>
|
||||||
|
<string name="for_all_ages">För alla åldrar</string>
|
||||||
|
<string name="up_to_age">Upp till %s år</string>
|
||||||
|
<string name="screensaver">Skärmsläckare</string>
|
||||||
|
<string name="screensaver_settings">Inställningar Skärmsläckare</string>
|
||||||
|
<string name="start_screensaver">Starta skärmsläckare</string>
|
||||||
|
<string name="photos">Bilder</string>
|
||||||
|
<string name="settings_saved">Inställningar sparade</string>
|
||||||
|
<string name="rotate_left">Rotera vänster</string>
|
||||||
|
<string name="rotate_right">Rotera höger</string>
|
||||||
|
<string name="zoom_in">Zooma in</string>
|
||||||
|
<string name="zoom_out">Zooma ut</string>
|
||||||
|
<string name="no_limit">Ingen gräns</string>
|
||||||
|
<string name="add_row">Lägg till rad</string>
|
||||||
|
<string name="press_back_to_cancel">Tryck bakåt för att avbryta</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@
|
||||||
<string name="check_for_updates">Güncellemeleri kontrol et</string>
|
<string name="check_for_updates">Güncellemeleri kontrol et</string>
|
||||||
<string name="combine_continue_next_summary">Yalnızca diziler için geçerlidir</string>
|
<string name="combine_continue_next_summary">Yalnızca diziler için geçerlidir</string>
|
||||||
<string name="combine_continue_next"><![CDATA[İzlemeye Devam Et ve Sıradaki Bölümleri Birleştir]]></string>
|
<string name="combine_continue_next"><![CDATA[İzlemeye Devam Et ve Sıradaki Bölümleri Birleştir]]></string>
|
||||||
<string name="direct_play_ass">ASS altyazıları doğrudan oynat</string>
|
<string name="direct_play_ass">ASS altyazıları için libass kullan</string>
|
||||||
<string name="direct_play_pgs">PGS altyazıları doğrudan oynat</string>
|
<string name="direct_play_pgs">PGS altyazıları doğrudan oynat</string>
|
||||||
<string name="downmix_stereo">Her zaman Stereo\'ya dönüştür</string>
|
<string name="downmix_stereo">Her zaman Stereo\'ya dönüştür</string>
|
||||||
<string name="ffmpeg_extension_pref">FFmpeg kod çözücü kullan</string>
|
<string name="ffmpeg_extension_pref">FFmpeg kod çözücü kullan</string>
|
||||||
|
|
@ -394,6 +394,7 @@
|
||||||
<string name="clear_track_choices">Parça seçimlerini temizle</string>
|
<string name="clear_track_choices">Parça seçimlerini temizle</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -472,4 +473,91 @@
|
||||||
<string name="display_presets_description">Tüm satırları hızlıca stilize etmek için hazır ayarlar</string>
|
<string name="display_presets_description">Tüm satırları hızlıca stilize etmek için hazır ayarlar</string>
|
||||||
<string name="favorite_items">Favori %s</string>
|
<string name="favorite_items">Favori %s</string>
|
||||||
<string name="customize_home_summary">Ana sayfadaki satırları ve görselleri seçin</string>
|
<string name="customize_home_summary">Ana sayfadaki satırları ve görselleri seçin</string>
|
||||||
|
<string name="content_scale_fit">Sığdır</string>
|
||||||
|
<string name="content_scale_crop">Kırp</string>
|
||||||
|
<string name="content_scale_fill">Doldur</string>
|
||||||
|
<string name="content_scale_fill_width">Genişliği Doldur</string>
|
||||||
|
<string name="content_scale_fill_height">Yüksekliği Doldur</string>
|
||||||
|
<string name="display_preset_default">Wholphin Varsayılan</string>
|
||||||
|
<string name="display_preset_compact">Wholphin Kompakt</string>
|
||||||
|
<string name="display_preset_series_thumb">Dizi Küçük Resimleri</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">Bölüm Küçük Resimleri</string>
|
||||||
|
<string name="volume_lowest">En Düşük</string>
|
||||||
|
<string name="volume_low">Düşük</string>
|
||||||
|
<string name="volume_medium">Orta</string>
|
||||||
|
<string name="volume_high">Yüksek</string>
|
||||||
|
<string name="volume_full">Tam</string>
|
||||||
|
<string name="purple">Mor</string>
|
||||||
|
<string name="orange">Turuncu</string>
|
||||||
|
<string name="bold_blue">Koyu Mavi</string>
|
||||||
|
<string name="black">Siyah</string>
|
||||||
|
<string name="skip_ignore">Yoksay</string>
|
||||||
|
<string name="skip_automatically">Otomatik Atla</string>
|
||||||
|
<string name="skip_ask">Atlamak için sor</string>
|
||||||
|
<string name="ffmpeg_fallback">Dahili çözücü yoksa FFmpeg kullan</string>
|
||||||
|
<string name="ffmpeg_prefer">Dahili çözücüler yerine FFmpeg\'i tercih et</string>
|
||||||
|
<string name="ffmpeg_never">FFmpeg çözücülerini asla kullanma</string>
|
||||||
|
<string name="next_up_playback_end">Oynatma sonunda</string>
|
||||||
|
<string name="next_up_outro">Kapanış bölümünde / Jenerik sırasında</string>
|
||||||
|
<string name="white">Beyaz</string>
|
||||||
|
<string name="light_gray">Açık Gri</string>
|
||||||
|
<string name="dark_gray">Koyu Gri</string>
|
||||||
|
<string name="yellow">Sarı</string>
|
||||||
|
<string name="cyan">Camgöbeği</string>
|
||||||
|
<string name="magenta">Macenta</string>
|
||||||
|
<string name="subtitle_edge_outline">Kenarlık</string>
|
||||||
|
<string name="subtitle_edge_shadow">Gölge</string>
|
||||||
|
<string name="background_style_wrap">Kapla</string>
|
||||||
|
<string name="background_style_boxed">Kutu içinde</string>
|
||||||
|
<string name="prefer_mpv">MPV\'yi tercih et</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">HDR oynatma için ExoPlayer kullan</string>
|
||||||
|
<string name="aspect_ratios_poster">Poster (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">Kare (1:1)</string>
|
||||||
|
<string name="image_type_primary">Birincil</string>
|
||||||
|
<string name="image_type_thumb">Küçük Resim</string>
|
||||||
|
<string name="backdrop_style_dynamic">Dinamik renkli görsel</string>
|
||||||
|
<string name="backdrop_style_image">Sadece görsel</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[URL ve API Anahtarını Girin]]></string>
|
||||||
|
<string name="api_key">API Anahtarı</string>
|
||||||
|
<string name="seerr_login">Seerr sunucusuna giriş yap</string>
|
||||||
|
<string name="seerr_jellyfin_user">Jellyfin kullanıcısı</string>
|
||||||
|
<string name="seerr_local_user">Yerel kullanıcı</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[Altyazı ara ve indir]]></string>
|
||||||
|
<string name="no_subtitles_found">Uzak altyazı bulunamadı</string>
|
||||||
|
<string name="series_continueing">Mevcut</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="one">%s dakika</item>
|
||||||
|
<item quantity="other">%s dakika</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">Başlama süresi</string>
|
||||||
|
<string name="animate">Oynatma Süresi</string>
|
||||||
|
<string name="max_age_rating">Maksimum yaş sınırı</string>
|
||||||
|
<string name="no_max">Sınır yok</string>
|
||||||
|
<string name="for_all_ages">Genel İzleyici</string>
|
||||||
|
<string name="up_to_age">%s yaşa kadar</string>
|
||||||
|
<string name="screensaver">Ekran koruyucu</string>
|
||||||
|
<string name="screensaver_settings">Ekran koruyucu ayarları</string>
|
||||||
|
<string name="start_screensaver">Ekran koruyucuyu başlat</string>
|
||||||
|
<string name="duration">Süre</string>
|
||||||
|
<string name="photos">Fotoğraflar</string>
|
||||||
|
<string name="include_types">İçerik türleri</string>
|
||||||
|
<string name="include_types_summary">Görsel gösterilecek öğe türleri</string>
|
||||||
|
<string name="in_app_screensaver">Uygulama içi ekran koruyucuyu kullan</string>
|
||||||
|
<string name="actor">Aktör</string>
|
||||||
|
<string name="composer">Besteci</string>
|
||||||
|
<string name="writer">Yazar</string>
|
||||||
|
<string name="guest_star">Konuk sanatçı</string>
|
||||||
|
<string name="producer">Yapımcı</string>
|
||||||
|
<string name="conductor">Orkestra şefi</string>
|
||||||
|
<string name="lyricist">Söz yazarı</string>
|
||||||
|
<string name="arranger">Aranjör</string>
|
||||||
|
<string name="engineer">Mühendis</string>
|
||||||
|
<string name="mixer">Karıştırıcı</string>
|
||||||
|
<string name="creator">Yaratıcı</string>
|
||||||
|
<string name="artist">Sanatçı</string>
|
||||||
|
<string name="delete_item">Bu öğeyi silmek istediğinizden emin misiniz?</string>
|
||||||
|
<string name="show_media_management">Medya yönetim seçeneklerini göster</string>
|
||||||
|
<string name="search_for">Ara %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,7 @@
|
||||||
<string name="clear_track_choices">Чіткий вибір доріжки</string>
|
<string name="clear_track_choices">Чіткий вибір доріжки</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@
|
||||||
<string name="font">字体</string>
|
<string name="font">字体</string>
|
||||||
<string name="background">背景</string>
|
<string name="background">背景</string>
|
||||||
<string name="success">成功</string>
|
<string name="success">成功</string>
|
||||||
<string name="official_rating">年龄分级</string>
|
<string name="official_rating">内容分级</string>
|
||||||
<string name="runtime_sort">播放时长</string>
|
<string name="runtime_sort">播放时长</string>
|
||||||
<string name="extras">附加内容</string>
|
<string name="extras">附加内容</string>
|
||||||
<plurals name="other_extras">
|
<plurals name="other_extras">
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
<string name="check_for_updates">检查更新</string>
|
<string name="check_for_updates">检查更新</string>
|
||||||
<string name="combine_continue_next_summary">仅适用于电视剧</string>
|
<string name="combine_continue_next_summary">仅适用于电视剧</string>
|
||||||
<string name="combine_continue_next"><![CDATA[合并继续观看与即将播放]]></string>
|
<string name="combine_continue_next"><![CDATA[合并继续观看与即将播放]]></string>
|
||||||
<string name="direct_play_ass">直接播放 ASS 字幕</string>
|
<string name="direct_play_ass">使用 libass 处理 ASS 字幕</string>
|
||||||
<string name="direct_play_pgs">直接播放 PGS 字幕</string>
|
<string name="direct_play_pgs">直接播放 PGS 字幕</string>
|
||||||
<string name="downmix_stereo">始终降混为立体声</string>
|
<string name="downmix_stereo">始终降混为立体声</string>
|
||||||
<string name="ffmpeg_extension_pref">使用 FFmpeg 解码模块</string>
|
<string name="ffmpeg_extension_pref">使用 FFmpeg 解码模块</string>
|
||||||
|
|
@ -351,6 +351,7 @@
|
||||||
<string name="only_forced_subtitles">仅强制字幕</string>
|
<string name="only_forced_subtitles">仅强制字幕</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -446,4 +447,90 @@
|
||||||
<string name="display_presets_description">可快速设置所有行样式的内置预设</string>
|
<string name="display_presets_description">可快速设置所有行样式的内置预设</string>
|
||||||
<string name="customize_home_summary">选择首页上的行和图片</string>
|
<string name="customize_home_summary">选择首页上的行和图片</string>
|
||||||
<string name="favorite_items">收藏的 %s</string>
|
<string name="favorite_items">收藏的 %s</string>
|
||||||
|
<string name="content_scale_fit">适应</string>
|
||||||
|
<string name="content_scale_crop">裁剪</string>
|
||||||
|
<string name="content_scale_fill">填充</string>
|
||||||
|
<string name="content_scale_fill_width">填充宽度</string>
|
||||||
|
<string name="content_scale_fill_height">填充高度</string>
|
||||||
|
<string name="display_preset_default">Wholphin 默认</string>
|
||||||
|
<string name="display_preset_compact">Wholphin 紧凑</string>
|
||||||
|
<string name="display_preset_series_thumb">剧集缩略图</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">分集缩略图</string>
|
||||||
|
<string name="purple">紫色</string>
|
||||||
|
<string name="orange">橙色</string>
|
||||||
|
<string name="bold_blue">深蓝色</string>
|
||||||
|
<string name="black">黑色</string>
|
||||||
|
<string name="skip_ignore">忽略</string>
|
||||||
|
<string name="skip_automatically">自动跳过</string>
|
||||||
|
<string name="skip_ask">询问后跳过</string>
|
||||||
|
<string name="ffmpeg_fallback">仅在无内置解码器时使用 FFmpeg</string>
|
||||||
|
<string name="ffmpeg_prefer">优先使用 FFmpeg 解码器而非内置解码器</string>
|
||||||
|
<string name="ffmpeg_never">从不使用 FFmpeg 解码器</string>
|
||||||
|
<string name="next_up_playback_end">播放结束时</string>
|
||||||
|
<string name="next_up_outro">播到片尾时</string>
|
||||||
|
<string name="white">白色</string>
|
||||||
|
<string name="light_gray">浅灰色</string>
|
||||||
|
<string name="dark_gray">深灰色</string>
|
||||||
|
<string name="yellow">黄色</string>
|
||||||
|
<string name="cyan">青色</string>
|
||||||
|
<string name="magenta">紫红色</string>
|
||||||
|
<string name="subtitle_edge_outline">轮廓</string>
|
||||||
|
<string name="subtitle_edge_shadow">阴影</string>
|
||||||
|
<string name="prefer_mpv">优先使用 mpv</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">HDR 播放使用 ExoPlayer</string>
|
||||||
|
<string name="aspect_ratios_poster">海报 (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">正方形 (1:1)</string>
|
||||||
|
<string name="image_type_primary">主图</string>
|
||||||
|
<string name="image_type_thumb">缩略图</string>
|
||||||
|
<string name="backdrop_style_dynamic">动态配色图片</string>
|
||||||
|
<string name="backdrop_style_image">仅图片</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[输入 URL 与 API 密钥]]></string>
|
||||||
|
<string name="api_key">API 密钥</string>
|
||||||
|
<string name="seerr_login">登录 Seerr 服务器</string>
|
||||||
|
<string name="seerr_jellyfin_user">Jellyfin 用户</string>
|
||||||
|
<string name="seerr_local_user">本地用户</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[搜索并下载字幕]]></string>
|
||||||
|
<string name="no_subtitles_found">未找到远程字幕</string>
|
||||||
|
<string name="volume_lowest">最低音量</string>
|
||||||
|
<string name="volume_low">低音量</string>
|
||||||
|
<string name="volume_medium">中等音量</string>
|
||||||
|
<string name="volume_high">高音量</string>
|
||||||
|
<string name="volume_full">最大音量</string>
|
||||||
|
<string name="background_style_wrap">环绕式</string>
|
||||||
|
<string name="background_style_boxed">框式</string>
|
||||||
|
<string name="series_continueing">当前</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="other">%s 分钟</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="animate">动画</string>
|
||||||
|
<string name="start_after">启动时间</string>
|
||||||
|
<string name="max_age_rating">最高年龄分级</string>
|
||||||
|
<string name="no_max">无上限</string>
|
||||||
|
<string name="for_all_ages">所有年龄</string>
|
||||||
|
<string name="up_to_age">最高至 %s 岁</string>
|
||||||
|
<string name="screensaver">屏幕保护程序</string>
|
||||||
|
<string name="screensaver_settings">屏幕保护程序设置</string>
|
||||||
|
<string name="start_screensaver">启动屏幕保护程序</string>
|
||||||
|
<string name="duration">时长</string>
|
||||||
|
<string name="photos">照片</string>
|
||||||
|
<string name="include_types">包含类型</string>
|
||||||
|
<string name="include_types_summary">要显示图片的项目类型</string>
|
||||||
|
<string name="in_app_screensaver">使用应用内屏幕保护程序</string>
|
||||||
|
<string name="actor">演员</string>
|
||||||
|
<string name="composer">作曲</string>
|
||||||
|
<string name="writer">编剧</string>
|
||||||
|
<string name="guest_star">客串</string>
|
||||||
|
<string name="producer">制片</string>
|
||||||
|
<string name="conductor">指挥</string>
|
||||||
|
<string name="lyricist">作词</string>
|
||||||
|
<string name="arranger">编曲</string>
|
||||||
|
<string name="engineer">录音</string>
|
||||||
|
<string name="mixer">混音</string>
|
||||||
|
<string name="creator">主创</string>
|
||||||
|
<string name="artist">艺人</string>
|
||||||
|
<string name="show_media_management">显示媒体管理选项</string>
|
||||||
|
<string name="delete_item">是否确定要删除此项目?</string>
|
||||||
|
<string name="search_for">搜索 %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
<string name="tv_guide">指南</string>
|
<string name="tv_guide">指南</string>
|
||||||
<string name="updates">更新</string>
|
<string name="updates">更新</string>
|
||||||
<string name="version">版本</string>
|
<string name="version">版本</string>
|
||||||
<string name="video_scale">畫面比例</string>
|
<string name="video_scale">畫面縮放比例</string>
|
||||||
<string name="video">影片</string>
|
<string name="video">影片</string>
|
||||||
<string name="videos">影片</string>
|
<string name="videos">影片</string>
|
||||||
<string name="watch_live">觀看直播</string>
|
<string name="watch_live">觀看直播</string>
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
<string name="direct_play_ass">直接播放 ASS 字幕</string>
|
<string name="direct_play_ass">直接播放 ASS 字幕</string>
|
||||||
<string name="direct_play_pgs">直接播放 PGS 字幕</string>
|
<string name="direct_play_pgs">直接播放 PGS 字幕</string>
|
||||||
<string name="ffmpeg_extension_pref">使用 FFmpeg 解碼模組</string>
|
<string name="ffmpeg_extension_pref">使用 FFmpeg 解碼模組</string>
|
||||||
<string name="global_content_scale">預設畫面比例</string>
|
<string name="global_content_scale">預設縮放比例</string>
|
||||||
<string name="install_update">安裝更新</string>
|
<string name="install_update">安裝更新</string>
|
||||||
<string name="installed_version">安裝版本</string>
|
<string name="installed_version">安裝版本</string>
|
||||||
<string name="max_homepage_items">首頁每列項目上限</string>
|
<string name="max_homepage_items">首頁每列項目上限</string>
|
||||||
|
|
@ -349,6 +349,7 @@
|
||||||
<string name="no_trailers">無預告片</string>
|
<string name="no_trailers">無預告片</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -362,7 +363,7 @@
|
||||||
<string name="remove_seerr_server">移除 Seerr 伺服器</string>
|
<string name="remove_seerr_server">移除 Seerr 伺服器</string>
|
||||||
<string name="seerr_server_added">已新增 Seerr 伺服器</string>
|
<string name="seerr_server_added">已新增 Seerr 伺服器</string>
|
||||||
<string name="password">密碼</string>
|
<string name="password">密碼</string>
|
||||||
<string name="username">使用者名稱</string>
|
<string name="username">帳號</string>
|
||||||
<string name="url">URL</string>
|
<string name="url">URL</string>
|
||||||
<string name="trending">當前趨勢</string>
|
<string name="trending">當前趨勢</string>
|
||||||
<string name="upcoming_movies">即將上映的電影</string>
|
<string name="upcoming_movies">即將上映的電影</string>
|
||||||
|
|
@ -427,18 +428,18 @@
|
||||||
<string name="suggestions_for">推薦的 %1$s</string>
|
<string name="suggestions_for">推薦的 %1$s</string>
|
||||||
<string name="increase_all_cards_size">放大所有海報尺寸</string>
|
<string name="increase_all_cards_size">放大所有海報尺寸</string>
|
||||||
<string name="decrease_all_cards_size">縮小所有海報尺寸</string>
|
<string name="decrease_all_cards_size">縮小所有海報尺寸</string>
|
||||||
<string name="use_thumb_images">使用橫向縮圖</string>
|
<string name="use_thumb_images">使用橫式縮圖</string>
|
||||||
<string name="add_row">新增一列</string>
|
<string name="add_row">新增一列</string>
|
||||||
<string name="apply_all_rows">套用到所有列</string>
|
<string name="apply_all_rows">套用到所有列</string>
|
||||||
<string name="home_rows">首頁各列</string>
|
<string name="home_rows">首頁各列</string>
|
||||||
<string name="add_row_for">為 %1$s 新增列項目</string>
|
<string name="add_row_for">為 %1$s 新增列項目</string>
|
||||||
<string name="display_presets_description">快速設定所有項目為內建預設樣式</string>
|
<string name="display_presets_description">快速設定所有項目為內建預設樣式</string>
|
||||||
<string name="customize_home_summary">選擇首頁要顯示的列項目及圖片樣式</string>
|
<string name="customize_home_summary">選擇首頁要顯示的項目及圖片樣式</string>
|
||||||
<string name="height">高度</string>
|
<string name="height">高度</string>
|
||||||
<string name="customize_home">自訂首頁</string>
|
<string name="customize_home">自訂首頁</string>
|
||||||
<string name="load_from_server">從伺服器載入設定</string>
|
<string name="load_from_server">從伺服器載入設定</string>
|
||||||
<string name="save_to_server">保存設定到伺服器</string>
|
<string name="save_to_server">保存設定到伺服器</string>
|
||||||
<string name="load_from_web_client">從網頁版載入</string>
|
<string name="load_from_web_client">從網頁版載入設定</string>
|
||||||
<string name="use_series">使用劇集圖片</string>
|
<string name="use_series">使用劇集圖片</string>
|
||||||
<string name="overwrite_server_settings">覆蓋伺服器上的設定?</string>
|
<string name="overwrite_server_settings">覆蓋伺服器上的設定?</string>
|
||||||
<string name="overwrite_local_settings">覆蓋本地設定?</string>
|
<string name="overwrite_local_settings">覆蓋本地設定?</string>
|
||||||
|
|
@ -450,11 +451,11 @@
|
||||||
<string name="content_scale_fill">拉伸填滿</string>
|
<string name="content_scale_fill">拉伸填滿</string>
|
||||||
<string name="content_scale_fill_width">填滿寬度</string>
|
<string name="content_scale_fill_width">填滿寬度</string>
|
||||||
<string name="content_scale_fill_height">填滿高度</string>
|
<string name="content_scale_fill_height">填滿高度</string>
|
||||||
<string name="volume_lowest">最低</string>
|
<string name="volume_lowest">最低音量</string>
|
||||||
<string name="volume_low">低</string>
|
<string name="volume_low">低音量</string>
|
||||||
<string name="volume_medium">中等</string>
|
<string name="volume_medium">中等音量</string>
|
||||||
<string name="volume_high">高</string>
|
<string name="volume_high">高音量</string>
|
||||||
<string name="volume_full">最大</string>
|
<string name="volume_full">最大音量</string>
|
||||||
<string name="purple">紫色</string>
|
<string name="purple">紫色</string>
|
||||||
<string name="orange">橘色</string>
|
<string name="orange">橘色</string>
|
||||||
<string name="bold_blue">深藍色</string>
|
<string name="bold_blue">深藍色</string>
|
||||||
|
|
@ -472,10 +473,64 @@
|
||||||
<string name="dark_gray">深灰色</string>
|
<string name="dark_gray">深灰色</string>
|
||||||
<string name="yellow">黃色</string>
|
<string name="yellow">黃色</string>
|
||||||
<string name="cyan">青色</string>
|
<string name="cyan">青色</string>
|
||||||
<string name="magenta">洋紅色</string>
|
<string name="magenta">桃紅色</string>
|
||||||
<string name="subtitle_edge_outline">外框</string>
|
<string name="subtitle_edge_outline">外框</string>
|
||||||
<string name="subtitle_edge_shadow">陰影</string>
|
<string name="subtitle_edge_shadow">陰影</string>
|
||||||
<string name="background_style_wrap">貼合文字</string>
|
<string name="background_style_wrap">貼合文字</string>
|
||||||
<string name="background_style_boxed">長方底框</string>
|
<string name="background_style_boxed">長方底框</string>
|
||||||
<string name="prefer_mpv">優先使用 MPV</string>
|
<string name="prefer_mpv">優先使用 MPV</string>
|
||||||
|
<string name="player_backend_options_subtitles_prefer_mpv">僅 HDR 使用 ExoPlayer 播放</string>
|
||||||
|
<string name="favorite_items">我的最愛的 %s</string>
|
||||||
|
<string name="display_preset_default">Wholphin 預設樣式</string>
|
||||||
|
<string name="display_preset_compact">Wholphin 緊湊樣式</string>
|
||||||
|
<string name="display_preset_series_thumb">劇集縮圖樣式</string>
|
||||||
|
<string name="display_preset_episode_thumbnails">單集縮圖樣式</string>
|
||||||
|
<string name="aspect_ratios_poster">海報 (2:3)</string>
|
||||||
|
<string name="aspect_ratios_16_9">16:9</string>
|
||||||
|
<string name="aspect_ratios_4_3">4:3</string>
|
||||||
|
<string name="aspect_ratios_square">正方形 (1:1)</string>
|
||||||
|
<string name="image_type_primary">主封面</string>
|
||||||
|
<string name="image_type_thumb">橫式縮圖</string>
|
||||||
|
<string name="backdrop_style_dynamic">動態色彩背景</string>
|
||||||
|
<string name="backdrop_style_image">僅背景圖</string>
|
||||||
|
<string name="enter_url_api_key"><![CDATA[輸入 URL 及 API 金鑰]]></string>
|
||||||
|
<string name="api_key">API 金鑰</string>
|
||||||
|
<string name="seerr_login">登入 Seerr 伺服器</string>
|
||||||
|
<string name="seerr_jellyfin_user">Jellyfin 帳號</string>
|
||||||
|
<string name="seerr_local_user">Seerr 帳號</string>
|
||||||
|
<string name="search_and_download_subtitles"><![CDATA[搜尋和下載字幕]]></string>
|
||||||
|
<string name="no_subtitles_found">未找到遠端字幕</string>
|
||||||
|
<string name="series_continueing">至今</string>
|
||||||
|
<plurals name="minutes">
|
||||||
|
<item quantity="other">%s 分鐘</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="start_after">啟動時間</string>
|
||||||
|
<string name="animate">動態效果</string>
|
||||||
|
<string name="max_age_rating">年齡分級上限</string>
|
||||||
|
<string name="no_max">不限制</string>
|
||||||
|
<string name="for_all_ages">適合所有年齡</string>
|
||||||
|
<string name="up_to_age">最高至 %s 歲</string>
|
||||||
|
<string name="screensaver">螢幕保護程式</string>
|
||||||
|
<string name="screensaver_settings">螢幕保護程式設定</string>
|
||||||
|
<string name="start_screensaver">立即開始</string>
|
||||||
|
<string name="duration">切換間隔</string>
|
||||||
|
<string name="photos">照片</string>
|
||||||
|
<string name="include_types">包含類型</string>
|
||||||
|
<string name="include_types_summary">要顯示圖片的類型</string>
|
||||||
|
<string name="in_app_screensaver">使用內建螢幕保護程式</string>
|
||||||
|
<string name="guest_star">客串演員</string>
|
||||||
|
<string name="actor">演員</string>
|
||||||
|
<string name="composer">作曲</string>
|
||||||
|
<string name="writer">編劇</string>
|
||||||
|
<string name="producer">製作人</string>
|
||||||
|
<string name="conductor">指揮</string>
|
||||||
|
<string name="lyricist">作詞</string>
|
||||||
|
<string name="arranger">編曲</string>
|
||||||
|
<string name="engineer">錄音師</string>
|
||||||
|
<string name="mixer">混音師</string>
|
||||||
|
<string name="creator">創作者</string>
|
||||||
|
<string name="artist">演出者</string>
|
||||||
|
<string name="delete_item">確定要刪除此項目嗎?</string>
|
||||||
|
<string name="show_media_management">顯示媒體管理選項</string>
|
||||||
|
<string name="search_for">搜尋 %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -451,6 +451,7 @@
|
||||||
<string name="clear_track_choices">Clear track choices</string>
|
<string name="clear_track_choices">Clear track choices</string>
|
||||||
<string name="dts_x">DTS:X</string>
|
<string name="dts_x">DTS:X</string>
|
||||||
<string name="dts_hd">DTS:HD</string>
|
<string name="dts_hd">DTS:HD</string>
|
||||||
|
<string name="dts_hd_ma">DTS:HD MA</string>
|
||||||
<string name="truehd">TrueHD</string>
|
<string name="truehd">TrueHD</string>
|
||||||
<string name="dolby_digital">DD</string>
|
<string name="dolby_digital">DD</string>
|
||||||
<string name="dolby_digital_plus">DD+</string>
|
<string name="dolby_digital_plus">DD+</string>
|
||||||
|
|
@ -719,5 +720,6 @@
|
||||||
<string name="creator">Creator</string>
|
<string name="creator">Creator</string>
|
||||||
<string name="artist">Artist</string>
|
<string name="artist">Artist</string>
|
||||||
<string name="search_for">Search %s</string>
|
<string name="search_for">Search %s</string>
|
||||||
|
<string name="select_all">Select all</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -711,6 +711,8 @@ components:
|
||||||
type: boolean
|
type: boolean
|
||||||
series4kEnabled:
|
series4kEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
cacheImages:
|
||||||
|
type: boolean
|
||||||
MovieResult:
|
MovieResult:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|
@ -1086,6 +1088,11 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Episode'
|
$ref: '#/components/schemas/Episode'
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
description: Status of the request. 1 = PENDING APPROVAL, 2 = APPROVED, 3 = DECLINED
|
||||||
|
readOnly: true
|
||||||
TvDetails:
|
TvDetails:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1268,6 +1275,10 @@ components:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
seasons:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Season'
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- status
|
- status
|
||||||
|
|
@ -6135,16 +6146,10 @@ paths:
|
||||||
type: integer
|
type: integer
|
||||||
example: 123
|
example: 123
|
||||||
seasons:
|
seasons:
|
||||||
# oneOf:
|
type: array
|
||||||
# - type: array
|
items:
|
||||||
# items:
|
type: integer
|
||||||
# type: integer
|
minimum: 0
|
||||||
# minimum: 0
|
|
||||||
# - type: string
|
|
||||||
# enum: [all]
|
|
||||||
# TODO
|
|
||||||
type: string
|
|
||||||
enum: [all]
|
|
||||||
nullable: true
|
nullable: true
|
||||||
is4k:
|
is4k:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.github.damontecres.wholphin.test
|
package com.github.damontecres.wholphin.test
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl
|
||||||
import com.github.damontecres.wholphin.ui.setup.seerr.createUrls
|
import com.github.damontecres.wholphin.ui.setup.seerr.createUrls
|
||||||
import org.junit.Assert
|
import com.github.damontecres.wholphin.ui.setup.seerr.migrateSeerrUrl
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class TestSeerr {
|
class TestSeerr {
|
||||||
|
|
@ -13,12 +15,12 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"http://jellyseerr.com/api/v1",
|
"http://jellyseerr.com/",
|
||||||
"https://jellyseerr.com/api/v1",
|
"https://jellyseerr.com/",
|
||||||
"http://jellyseerr.com:5055/api/v1",
|
"http://jellyseerr.com:5055/",
|
||||||
"https://jellyseerr.com:5055/api/v1",
|
"https://jellyseerr.com:5055/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -29,10 +31,10 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"https://jellyseerr.com/api/v1",
|
"https://jellyseerr.com/",
|
||||||
"https://jellyseerr.com:5055/api/v1",
|
"https://jellyseerr.com:5055/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -43,10 +45,10 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"http://jellyseerr.com/api/v1",
|
"http://jellyseerr.com/",
|
||||||
"http://jellyseerr.com:5055/api/v1",
|
"http://jellyseerr.com:5055/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -57,10 +59,10 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"http://jellyseerr.com:5055/api/v1",
|
"http://jellyseerr.com:5055/",
|
||||||
"https://jellyseerr.com:5055/api/v1",
|
"https://jellyseerr.com:5055/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -71,10 +73,10 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"http://10.0.0.2:443/api/v1",
|
"http://10.0.0.2:443/",
|
||||||
"https://10.0.0.2/api/v1",
|
"https://10.0.0.2/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -85,9 +87,87 @@ class TestSeerr {
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
listOf(
|
listOf(
|
||||||
"http://10.0.0.2:8080/api/v1",
|
"http://10.0.0.2:8080/",
|
||||||
"https://10.0.0.2:8080/api/v1",
|
"https://10.0.0.2:8080/",
|
||||||
)
|
)
|
||||||
Assert.assertEquals(expected, urls)
|
assertEquals(expected, urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCreateUrls7() {
|
||||||
|
val urls =
|
||||||
|
createUrls("http://10.0.0.2:80")
|
||||||
|
.map { it.toString() }
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
listOf(
|
||||||
|
"http://10.0.0.2/",
|
||||||
|
"http://10.0.0.2:5055/",
|
||||||
|
)
|
||||||
|
assertEquals(expected, urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCreateUrls8() {
|
||||||
|
val urls =
|
||||||
|
createUrls("https://10.0.0.2:443")
|
||||||
|
.map { it.toString() }
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
listOf(
|
||||||
|
"https://10.0.0.2/",
|
||||||
|
"https://10.0.0.2:5055/",
|
||||||
|
)
|
||||||
|
assertEquals(expected, urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test createUrls for path`() {
|
||||||
|
val urls =
|
||||||
|
createUrls("https://jellyseerr.com/seerr/")
|
||||||
|
.map { it.toString() }
|
||||||
|
|
||||||
|
val expected =
|
||||||
|
listOf(
|
||||||
|
"https://jellyseerr.com/seerr/",
|
||||||
|
"https://jellyseerr.com:5055/seerr/",
|
||||||
|
)
|
||||||
|
assertEquals(expected, urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test build api url`() {
|
||||||
|
var url = "https://jellyseerr.com/"
|
||||||
|
assertEquals("https://jellyseerr.com/api/v1", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "https://jellyseerr.com/path"
|
||||||
|
assertEquals("https://jellyseerr.com/path/api/v1", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "http://jellyseerr.com:5055/"
|
||||||
|
assertEquals("http://jellyseerr.com:5055/api/v1", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "http://jellyseerr.com:7878/path/"
|
||||||
|
assertEquals("http://jellyseerr.com:7878/path/api/v1", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "http://jellyseerr.com/api/v1"
|
||||||
|
assertEquals("http://jellyseerr.com/api/v1", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "http://jellyseerr.com/api/v1/"
|
||||||
|
assertEquals("http://jellyseerr.com/api/v1/", createSeerrApiUrl(url))
|
||||||
|
|
||||||
|
url = "http://jellyseerr.com/path/api/v1"
|
||||||
|
assertEquals("http://jellyseerr.com/path/api/v1", createSeerrApiUrl(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test migration`() {
|
||||||
|
assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/api/v1"))
|
||||||
|
assertEquals("https://10.0.0.2/", migrateSeerrUrl("https://10.0.0.2/api/v1"))
|
||||||
|
assertEquals("http://10.0.0.2:5055/", migrateSeerrUrl("http://10.0.0.2:5055/api/v1"))
|
||||||
|
assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/api/v1/"))
|
||||||
|
assertEquals("http://10.0.0.2/path/", migrateSeerrUrl("http://10.0.0.2/path/api/v1"))
|
||||||
|
assertEquals("http://10.0.0.2/api/v1/", migrateSeerrUrl("http://10.0.0.2/api/v1/api/v1"))
|
||||||
|
assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/"))
|
||||||
|
assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.github.damontecres.wholphin.test
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.services.getDownloadUrl
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
|
class TestUpdateChecker {
|
||||||
|
lateinit var releaseJson: JsonObject
|
||||||
|
val assetsJson: JsonArray by lazy { releaseJson["assets"]!!.jsonArray }
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
val resource = javaClass.classLoader?.getResource("release_develop.json")
|
||||||
|
Assert.assertNotNull(resource)
|
||||||
|
val fileContents = Paths.get(resource!!.toURI()).readText()
|
||||||
|
releaseJson = Json.parseToJsonElement(fileContents).jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Release chooses release`() {
|
||||||
|
val url = getDownloadUrl(assetsJson, false, listOf())
|
||||||
|
Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Choose abi`() {
|
||||||
|
val url = getDownloadUrl(assetsJson, false, listOf("arm64-v8a"))
|
||||||
|
Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-arm64-v8a.apk", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Choose unknown abi`() {
|
||||||
|
val url = getDownloadUrl(assetsJson, false, listOf("unknown"))
|
||||||
|
Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Debug chooses debug`() {
|
||||||
|
val url = getDownloadUrl(assetsJson, true, listOf())
|
||||||
|
Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug.apk", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Choose debug abi`() {
|
||||||
|
val url = getDownloadUrl(assetsJson, true, listOf("arm64-v8a"))
|
||||||
|
Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-arm64-v8a.apk", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
475
app/src/test/resources/release_develop.json
Normal file
475
app/src/test/resources/release_develop.json
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/297064448",
|
||||||
|
"assets_url": "https://api.github.com/repos/damontecres/Wholphin/releases/297064448/assets",
|
||||||
|
"upload_url": "https://uploads.github.com/repos/damontecres/Wholphin/releases/297064448/assets{?name,label}",
|
||||||
|
"html_url": "https://github.com/damontecres/Wholphin/releases/tag/develop",
|
||||||
|
"id": 297064448,
|
||||||
|
"author": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"node_id": "RE_kwDOPzwRps4RtNgA",
|
||||||
|
"tag_name": "develop",
|
||||||
|
"target_commitish": "main",
|
||||||
|
"name": "v0.5.3-8-g627b89f1",
|
||||||
|
"draft": false,
|
||||||
|
"immutable": false,
|
||||||
|
"prerelease": true,
|
||||||
|
"created_at": "2026-03-14T20:47:08Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:31Z",
|
||||||
|
"published_at": "2026-03-14T20:54:31Z",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937404",
|
||||||
|
"id": 373937404,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdT8",
|
||||||
|
"name": "Wholphin-debug-0.5.3-8-g627b89f1-44-arm64-v8a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 65936692,
|
||||||
|
"digest": "sha256:ede68f14374dea27a92063d1aab765265cf5386c93533b01e7c666c4b8a6ea99",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:23Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:26Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44-arm64-v8a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937407",
|
||||||
|
"id": 373937407,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdT_",
|
||||||
|
"name": "Wholphin-debug-0.5.3-8-g627b89f1-44-armeabi-v7a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 62726455,
|
||||||
|
"digest": "sha256:c2c6d0443af1302a8aa8a70426605e6ca67345b98a74b5b1d22b85e7de9012c2",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:24Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:25Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44-armeabi-v7a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937406",
|
||||||
|
"id": 373937406,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdT-",
|
||||||
|
"name": "Wholphin-debug-0.5.3-8-g627b89f1-44.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 99552740,
|
||||||
|
"digest": "sha256:d0f5bbbe0c6f6bc31f681d70d60a5730dd0dbc83f28e66922fde27f4e60cda05",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:24Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:26Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937405",
|
||||||
|
"id": 373937405,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdT9",
|
||||||
|
"name": "Wholphin-debug-arm64-v8a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 65936692,
|
||||||
|
"digest": "sha256:ede68f14374dea27a92063d1aab765265cf5386c93533b01e7c666c4b8a6ea99",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:23Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:26Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-arm64-v8a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937408",
|
||||||
|
"id": 373937408,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUA",
|
||||||
|
"name": "Wholphin-debug-armeabi-v7a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 62726455,
|
||||||
|
"digest": "sha256:c2c6d0443af1302a8aa8a70426605e6ca67345b98a74b5b1d22b85e7de9012c2",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:24Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:26Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-armeabi-v7a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937422",
|
||||||
|
"id": 373937422,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUO",
|
||||||
|
"name": "Wholphin-debug.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 99552740,
|
||||||
|
"digest": "sha256:d0f5bbbe0c6f6bc31f681d70d60a5730dd0dbc83f28e66922fde27f4e60cda05",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:26Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:29Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937424",
|
||||||
|
"id": 373937424,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUQ",
|
||||||
|
"name": "Wholphin-release-0.5.3-8-g627b89f1-44-arm64-v8a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 56651710,
|
||||||
|
"digest": "sha256:bf010482a8b8626fb93bbb3285667b22a41c3548286d3319bda92c71eedba672",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:26Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:28Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44-arm64-v8a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937427",
|
||||||
|
"id": 373937427,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUT",
|
||||||
|
"name": "Wholphin-release-0.5.3-8-g627b89f1-44-armeabi-v7a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 53441542,
|
||||||
|
"digest": "sha256:5b8aee8f79fd5ae26dbd5de4a1b4d38b90bc6b35cd2e7fa5f2683909463da390",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:26Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:28Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44-armeabi-v7a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937431",
|
||||||
|
"id": 373937431,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUX",
|
||||||
|
"name": "Wholphin-release-0.5.3-8-g627b89f1-44.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 90267831,
|
||||||
|
"digest": "sha256:bc10956a072a5a07c372a67ee8732171470529b468fd7d85a277a738604c34c5",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:27Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:29Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937439",
|
||||||
|
"id": 373937439,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUf",
|
||||||
|
"name": "Wholphin-release-arm64-v8a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 56651710,
|
||||||
|
"digest": "sha256:bf010482a8b8626fb93bbb3285667b22a41c3548286d3319bda92c71eedba672",
|
||||||
|
"download_count": 1,
|
||||||
|
"created_at": "2026-03-14T20:54:27Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:29Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-arm64-v8a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937452",
|
||||||
|
"id": 373937452,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUs",
|
||||||
|
"name": "Wholphin-release-armeabi-v7a.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 53441542,
|
||||||
|
"digest": "sha256:5b8aee8f79fd5ae26dbd5de4a1b4d38b90bc6b35cd2e7fa5f2683909463da390",
|
||||||
|
"download_count": 0,
|
||||||
|
"created_at": "2026-03-14T20:54:28Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:30Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-armeabi-v7a.apk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937453",
|
||||||
|
"id": 373937453,
|
||||||
|
"node_id": "RA_kwDOPzwRps4WSdUt",
|
||||||
|
"name": "Wholphin-release.apk",
|
||||||
|
"label": "",
|
||||||
|
"uploader": {
|
||||||
|
"login": "github-actions[bot]",
|
||||||
|
"id": 41898282,
|
||||||
|
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||||
|
"html_url": "https://github.com/apps/github-actions",
|
||||||
|
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||||
|
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||||
|
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||||
|
"type": "Bot",
|
||||||
|
"user_view_type": "public",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"content_type": "application/vnd.android.package-archive",
|
||||||
|
"state": "uploaded",
|
||||||
|
"size": 90267831,
|
||||||
|
"digest": "sha256:bc10956a072a5a07c372a67ee8732171470529b468fd7d85a277a738604c34c5",
|
||||||
|
"download_count": 1,
|
||||||
|
"created_at": "2026-03-14T20:54:28Z",
|
||||||
|
"updated_at": "2026-03-14T20:54:31Z",
|
||||||
|
"browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tarball_url": "https://api.github.com/repos/damontecres/Wholphin/tarball/develop",
|
||||||
|
"zipball_url": "https://api.github.com/repos/damontecres/Wholphin/zipball/develop",
|
||||||
|
"body": "### `main` development build\n\nThis pre-release tracks the development build of Wholphin from the `main` unstable branch.\n\nSee https://github.com/damontecres/Wholphin/releases/latest for the latest stable release.\n\nIf you want to update to this version in-app, change Settings->Advanced->Update URL to https://api.github.com/repos/damontecres/Wholphin/releases/tags/develop (replace `latest` with `tags/develop`).\n\n[See changes since `v0.5.3`](https://github.com/damontecres/Wholphin/compare/v0.5.3...develop)\n"
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,11 @@ desugar_jdk_libs = "2.1.5"
|
||||||
hiltNavigationCompose = "1.3.0"
|
hiltNavigationCompose = "1.3.0"
|
||||||
hiltWork = "1.3.0"
|
hiltWork = "1.3.0"
|
||||||
kache = "2.1.1"
|
kache = "2.1.1"
|
||||||
kotlin = "2.3.10"
|
kotlin = "2.3.20"
|
||||||
ksp = "2.3.6"
|
ksp = "2.3.6"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.18.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
composeBom = "2026.02.01"
|
composeBom = "2026.03.00"
|
||||||
mockk = "1.14.9"
|
mockk = "1.14.9"
|
||||||
robolectric = "4.16.1"
|
robolectric = "4.16.1"
|
||||||
multiplatformMarkdownRenderer = "0.39.2"
|
multiplatformMarkdownRenderer = "0.39.2"
|
||||||
|
|
@ -20,18 +20,18 @@ okhttpBom = "5.3.2"
|
||||||
programguide = "1.6.0"
|
programguide = "1.6.0"
|
||||||
slf4j2Timber = "1.2"
|
slf4j2Timber = "1.2"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tvFoundation = "1.0.0-alpha12"
|
tvFoundation = "1.0.0-beta01"
|
||||||
tvMaterial = "1.0.1"
|
tvMaterial = "1.0.1"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
activityCompose = "1.12.4"
|
activityCompose = "1.13.0"
|
||||||
androidx-media3 = "1.9.2"
|
androidx-media3 = "1.9.3"
|
||||||
coil = "3.4.0"
|
coil = "3.4.0"
|
||||||
jellyfin-sdk = "1.7.1"
|
jellyfin-sdk = "1.7.1"
|
||||||
nav3Core = "1.0.1"
|
nav3Core = "1.0.1"
|
||||||
lifecycleViewmodelNav3 = "2.10.0"
|
lifecycleViewmodelNav3 = "2.10.0"
|
||||||
material3AdaptiveNav3 = "1.0.0-alpha03"
|
material3AdaptiveNav3 = "1.0.0-alpha03"
|
||||||
protobuf = "0.9.6"
|
protobuf = "0.9.6"
|
||||||
datastore = "1.2.0"
|
datastore = "1.2.1"
|
||||||
kotlinx-serialization = "1.10.0"
|
kotlinx-serialization = "1.10.0"
|
||||||
protobuf-javalite = "4.34.0"
|
protobuf-javalite = "4.34.0"
|
||||||
hilt = "2.59.2"
|
hilt = "2.59.2"
|
||||||
|
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
||||||
2
gradlew
vendored
2
gradlew
vendored
|
|
@ -57,7 +57,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@ clone "https://gitlab.freedesktop.org/freetype/freetype.git" "VER-2-14-1" freety
|
||||||
|
|
||||||
clone "https://github.com/libass/libass" "0.17.4" libass
|
clone "https://github.com/libass/libass" "0.17.4" libass
|
||||||
|
|
||||||
clone "https://github.com/haasn/libplacebo" "v7.351.0" libplacebo --recurse-submodules
|
rm -rf libplacebo
|
||||||
|
git clone "https://github.com/haasn/libplacebo" --single-branch -b "master" --recurse-submodules libplacebo
|
||||||
|
pushd libplacebo || exit
|
||||||
|
git checkout 1bd8d2d6a715bc870bdffa6759e62c419000dd51
|
||||||
|
popd || exit
|
||||||
|
|
||||||
clone "https://github.com/mpv-player/mpv" "v0.41.0" mpv
|
clone "https://github.com/mpv-player/mpv" "v0.41.0" mpv
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue