Add in-app & Android TV screensaver (#946)
## Description Adds both an optional in-app and Android TV OS (DreamService) screensaver Has settings for: - Starting delay, options from 30s to 1 hour - Duration to show each image, 30s to 2 minutes - Show clock (separate from app-wide clock) - Toggle animated zooming - Set a max age rating to limit what content is shown - Included media types: movies, tv shows, and/or photos By default only Movies & TV Shows are included. But if you have like family albums on your server, you could enable Photos too. There's also an button to start the screensaver so you can see what it looks like. ### Related issues Closes #230 ### Testing Emulator, Onn 4k pro, nvidia shield using both in-app and OS screensavers ## Screenshots  ## AI or LLM usage None ## Acknowledgements Some of the skeleton code for setting up the `DreamService` with the right lifecycle was copied and modified from the [official app's implementation](https://github.com/jellyfin/jellyfin-androidtv/blob/master/app/src/main/java/org/jellyfin/androidtv/integration/dream/DreamServiceCompat.kt).
This commit is contained in:
parent
536e8d366c
commit
a7e86fbc15
25 changed files with 954 additions and 78 deletions
|
|
@ -997,6 +997,12 @@ sealed interface AppPreference<Pref, T> {
|
|||
summaryOn = R.string.enabled,
|
||||
summaryOff = R.string.disabled,
|
||||
)
|
||||
|
||||
val ScreensaverSettings =
|
||||
AppDestinationPreference<AppPreferences>(
|
||||
title = R.string.screensaver_settings,
|
||||
destination = Destination.Settings(PreferenceScreenOption.SCREENSAVER),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1011,6 +1017,7 @@ val basicPreferences =
|
|||
AppPreference.RememberSelectedTab,
|
||||
AppPreference.SubtitleStyle,
|
||||
AppPreference.ThemeColors,
|
||||
AppPreference.ScreensaverSettings,
|
||||
),
|
||||
),
|
||||
PreferenceGroup(
|
||||
|
|
@ -1201,6 +1208,24 @@ val liveTvPreferences =
|
|||
AppPreference.LiveTvColorCodePrograms,
|
||||
)
|
||||
|
||||
val screensaverPreferences =
|
||||
listOf(
|
||||
PreferenceGroup(
|
||||
title = R.string.screensaver,
|
||||
preferences =
|
||||
listOf(
|
||||
ScreensaverPreference.Enabled,
|
||||
ScreensaverPreference.StartDelay,
|
||||
ScreensaverPreference.Duration,
|
||||
ScreensaverPreference.ShowClock,
|
||||
ScreensaverPreference.Animate,
|
||||
ScreensaverPreference.MaxAge,
|
||||
ScreensaverPreference.ItemTypes,
|
||||
ScreensaverPreference.Start,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
data class AppSwitchPreference<Pref>(
|
||||
@get:StringRes override val title: Int,
|
||||
override val defaultValue: Boolean,
|
||||
|
|
@ -1255,8 +1280,6 @@ data class AppMultiChoicePreference<Pref, T>(
|
|||
override val getter: (prefs: Pref) -> List<T>,
|
||||
override val setter: (prefs: Pref, value: List<T>) -> Pref,
|
||||
@param:StringRes val summary: Int? = null,
|
||||
val toSharedPrefs: (T) -> String,
|
||||
val fromSharedPrefs: (String) -> T?,
|
||||
) : AppPreference<Pref, List<T>>
|
||||
|
||||
data class AppClickablePreference<Pref>(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.datastore.core.CorruptionException
|
|||
import androidx.datastore.core.Serializer
|
||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.inject.Inject
|
||||
|
|
@ -120,6 +121,20 @@ class AppPreferencesSerializer
|
|||
colorCodePrograms =
|
||||
AppPreference.LiveTvColorCodePrograms.defaultValue
|
||||
}.build()
|
||||
|
||||
screensaverPreference =
|
||||
ScreensaverPreferences
|
||||
.newBuilder()
|
||||
.apply {
|
||||
startDelay = ScreensaverPreference.DEFAULT_START_DELAY
|
||||
duration = ScreensaverPreference.DEFAULT_DURATION
|
||||
animate = ScreensaverPreference.Animate.defaultValue
|
||||
maxAgeFilter = ScreensaverPreference.DEFAULT_MAX_AGE
|
||||
showClock = ScreensaverPreference.ShowClock.defaultValue
|
||||
clearItemTypes()
|
||||
addItemTypes(BaseItemKind.MOVIE.serialName)
|
||||
addItemTypes(BaseItemKind.SERIES.serialName)
|
||||
}.build()
|
||||
}.build()
|
||||
|
||||
advancedPreferences =
|
||||
|
|
@ -200,6 +215,11 @@ inline fun AppPreferences.updatePhotoPreferences(block: PhotoPreferences.Builder
|
|||
photoPreferences = photoPreferences.toBuilder().apply(block).build()
|
||||
}
|
||||
|
||||
inline fun AppPreferences.updateScreensaverPreferences(block: ScreensaverPreferences.Builder.() -> Unit): AppPreferences =
|
||||
updateInterfacePreferences {
|
||||
screensaverPreference = screensaverPreference.toBuilder().apply(block).build()
|
||||
}
|
||||
|
||||
fun SubtitlePreferences.Builder.resetSubtitles() {
|
||||
fontSize = SubtitleSettings.FontSize.defaultValue.toInt()
|
||||
fontColor = SubtitleSettings.FontColor.defaultValue.toArgb()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
package com.github.damontecres.wholphin.preferences
|
||||
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.WholphinApplication
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object ScreensaverPreference {
|
||||
val Enabled =
|
||||
AppSwitchPreference<AppPreferences>(
|
||||
title = R.string.in_app_screensaver,
|
||||
defaultValue = false,
|
||||
getter = { it.interfacePreferences.screensaverPreference.enabled },
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences { enabled = value }
|
||||
},
|
||||
summaryOn = R.string.yes,
|
||||
summaryOff = R.string.no,
|
||||
)
|
||||
|
||||
const val DEFAULT_START_DELAY = 15 * 60_000L
|
||||
private val startDelayValues =
|
||||
listOf(
|
||||
30_000L,
|
||||
60_000L,
|
||||
2 * 60_000L,
|
||||
5 * 60_000L,
|
||||
10 * 60_000L,
|
||||
15 * 60_000L,
|
||||
30 * 60_000L,
|
||||
60 * 60_000L,
|
||||
)
|
||||
val StartDelay =
|
||||
AppSliderPreference<AppPreferences>(
|
||||
title = R.string.start_after,
|
||||
defaultValue = startDelayValues.indexOf(DEFAULT_START_DELAY).toLong(),
|
||||
min = 0,
|
||||
max = startDelayValues.size - 1L,
|
||||
interval = 1,
|
||||
getter = {
|
||||
startDelayValues.indexOf(it.interfacePreferences.screensaverPreference.startDelay).toLong()
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences {
|
||||
startDelay = startDelayValues[value.toInt()]
|
||||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
val v = startDelayValues.getOrNull(value.toInt()) ?: DEFAULT_START_DELAY
|
||||
v.milliseconds.toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const val DEFAULT_DURATION = 30_000L
|
||||
private val durationValues =
|
||||
listOf(
|
||||
15_000L,
|
||||
30_000L,
|
||||
60_000L,
|
||||
2 * 60_000L,
|
||||
)
|
||||
val Duration =
|
||||
AppSliderPreference<AppPreferences>(
|
||||
title = R.string.duration,
|
||||
defaultValue = durationValues.indexOf(DEFAULT_DURATION).toLong(),
|
||||
min = 0,
|
||||
max = durationValues.size - 1L,
|
||||
interval = 1,
|
||||
getter = {
|
||||
durationValues.indexOf(it.interfacePreferences.screensaverPreference.duration).toLong()
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences {
|
||||
duration = durationValues[value.toInt()]
|
||||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
val v = durationValues.getOrNull(value.toInt()) ?: DEFAULT_DURATION
|
||||
v.milliseconds.toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val ShowClock =
|
||||
AppSwitchPreference<AppPreferences>(
|
||||
title = R.string.show_clock,
|
||||
defaultValue = AppPreference.ShowClock.defaultValue,
|
||||
getter = { it.interfacePreferences.screensaverPreference.showClock },
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences { showClock = value }
|
||||
},
|
||||
summaryOn = R.string.yes,
|
||||
summaryOff = R.string.no,
|
||||
)
|
||||
|
||||
val Animate =
|
||||
AppSwitchPreference<AppPreferences>(
|
||||
title = R.string.animate,
|
||||
defaultValue = true,
|
||||
getter = { it.interfacePreferences.screensaverPreference.animate },
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences { animate = value }
|
||||
},
|
||||
summaryOn = R.string.enabled,
|
||||
summaryOff = R.string.disabled,
|
||||
)
|
||||
|
||||
const val DEFAULT_MAX_AGE = 16
|
||||
private val maxAgeValues = listOf(0, 5, 10, 13, 14, 16, 18, 21, -1)
|
||||
val MaxAge =
|
||||
AppSliderPreference<AppPreferences>(
|
||||
title = R.string.max_age_rating,
|
||||
defaultValue = maxAgeValues.indexOf(DEFAULT_MAX_AGE).toLong(),
|
||||
min = 0,
|
||||
max = maxAgeValues.size - 1L,
|
||||
interval = 1,
|
||||
getter = {
|
||||
it.interfacePreferences.screensaverPreference.maxAgeFilter
|
||||
.takeIf { it >= 0 }
|
||||
?.let { maxAgeValues.indexOf(it).toLong() }
|
||||
?: maxAgeValues.lastIndex.toLong()
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences {
|
||||
maxAgeFilter = maxAgeValues[value.toInt()]
|
||||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
when (value) {
|
||||
null -> {
|
||||
null
|
||||
}
|
||||
|
||||
maxAgeValues.lastIndex.toLong() -> {
|
||||
WholphinApplication.instance.getString(R.string.no_max)
|
||||
}
|
||||
|
||||
0L -> {
|
||||
WholphinApplication.instance.getString(R.string.for_all_ages)
|
||||
}
|
||||
|
||||
else -> {
|
||||
WholphinApplication.instance.getString(
|
||||
R.string.up_to_age,
|
||||
maxAgeValues[value.toInt()].toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val ItemTypes =
|
||||
AppMultiChoicePreference<AppPreferences, BaseItemKind>(
|
||||
title = R.string.include_types,
|
||||
summary = R.string.include_types_summary,
|
||||
defaultValue = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
|
||||
allValues = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES, BaseItemKind.PHOTO),
|
||||
displayValues = R.array.screensaver_item_types,
|
||||
getter = {
|
||||
it.interfacePreferences.screensaverPreference.itemTypesList.map { type ->
|
||||
BaseItemKind.fromName(type)
|
||||
}
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updateScreensaverPreferences {
|
||||
clearItemTypes()
|
||||
addAllItemTypes(value.map { it.serialName })
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val Start =
|
||||
AppClickablePreference<AppPreferences>(
|
||||
title = R.string.start_screensaver,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue