Add settings to show the current time (#155)

Closes #154 

Adds a settings to show the current time in the upper right corner.

If enabled, it will also show the end time for the playback on the
overlay.
This commit is contained in:
damontecres 2025-11-04 15:27:38 -05:00 committed by GitHub
parent 40a8249469
commit 77c787b51f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 118 additions and 15 deletions

View file

@ -642,6 +642,18 @@ sealed interface AppPreference<T> {
indexToValue = { ShowNextUpWhen.forNumber(it) },
valueToIndex = { it.number },
)
val ShowClock =
AppSwitchPreference(
title = R.string.show_clock,
defaultValue = true,
getter = { it.interfacePreferences.showClock },
setter = { prefs, value ->
prefs.updateInterfacePreferences { showClock = value }
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
)
}
}
@ -709,6 +721,7 @@ val advancedPreferences =
title = R.string.ui_interface,
preferences =
listOf(
AppPreference.ShowClock,
AppPreference.CombineContinueNext,
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
// AppPreference.NavDrawerSwitchOnFocus,

View file

@ -73,6 +73,7 @@ class AppPreferencesSerializer
appThemeColors = AppPreference.ThemeColors.defaultValue
navDrawerSwitchOnFocus =
AppPreference.NavDrawerSwitchOnFocus.defaultValue
showClock = AppPreference.ShowClock.defaultValue
}.build()
}.build()

View file

@ -158,6 +158,7 @@ fun RecommendedMovie(
onClickItem = onClickItem,
onLongClickItem = {},
onFocusPosition = onFocusPosition,
showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier,
)
}

View file

@ -170,6 +170,7 @@ fun RecommendedTvShow(
onClickItem = onClickItem,
onLongClickItem = {},
onFocusPosition = onFocusPosition,
showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier,
)
}

View file

@ -132,6 +132,12 @@ fun SeriesOverviewContent(
modifier = Modifier,
) {
item {
val paddingValues =
if (preferences.appPreferences.interfacePreferences.showClock) {
PaddingValues(start = 16.dp, end = 100.dp)
} else {
PaddingValues(start = 16.dp, end = 16.dp)
}
TabRow(
selectedTabIndex = focusTabIndex,
modifier =
@ -140,7 +146,7 @@ fun SeriesOverviewContent(
focusRequesters.size > selectedTabIndex,
{ Modifier.focusRestorer(focusRequesters[selectedTabIndex]) },
).focusRequester(tabRowFocusRequester)
.padding(horizontal = 16.dp)
.padding(paddingValues)
.fillMaxWidth()
.onFocusChanged {
if (!it.isFocused) {

View file

@ -171,6 +171,7 @@ fun HomePage(
)
},
loadingState = loading,
showClock = preferences.appPreferences.interfacePreferences.showClock,
modifier = modifier,
)
dialog?.let { params ->
@ -205,6 +206,7 @@ fun HomePageContent(
homeRows: List<HomeRow>,
onClickItem: (BaseItem) -> Unit,
onLongClickItem: (BaseItem) -> Unit,
showClock: Boolean,
modifier: Modifier = Modifier,
onFocusPosition: ((RowColumn) -> Unit)? = null,
loadingState: LoadingState? = null,
@ -342,7 +344,7 @@ fun HomePageContent(
Box(
modifier =
Modifier
.padding(16.dp)
.padding(if (showClock) 40.dp else 20.dp)
.size(40.dp)
.align(Alignment.TopEnd),
) {

View file

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@ -27,6 +28,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@ -74,13 +76,16 @@ import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.ui.spacedByWithFooter
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.TimeFormatter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.DeviceProfile
import timber.log.Timber
import java.time.LocalTime
import java.util.UUID
import javax.inject.Inject
@ -431,13 +436,35 @@ fun NavDrawer(
}
},
) {
// Drawer content
DestinationContent(
destination = destination,
preferences = preferences,
deviceProfile = deviceProfile,
Box(
modifier = Modifier.fillMaxSize(),
)
) {
// Drawer content
DestinationContent(
destination = destination,
preferences = preferences,
deviceProfile = deviceProfile,
modifier = Modifier.fillMaxSize(),
)
if (preferences.appPreferences.interfacePreferences.showClock) {
var now by remember { mutableStateOf(LocalTime.now()) }
LaunchedEffect(Unit) {
if (isActive) {
now = LocalTime.now()
}
}
Text(
text = TimeFormatter.format(now),
// style = MaterialTheme.typography.titleMedium,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onSurface,
modifier =
Modifier
.align(Alignment.TopEnd)
.padding(vertical = 16.dp, horizontal = 24.dp),
)
}
}
}
}

View file

@ -282,6 +282,7 @@ fun SeekBar(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
val remaining = ((player.duration - position) / 1000).seconds
Text(
text = (position / 1000).seconds.toString(),
color = MaterialTheme.colorScheme.onSurface,
@ -291,7 +292,7 @@ fun SeekBar(
.padding(8.dp),
)
Text(
text = "-" + ((player.duration - position) / 1000).seconds.toString(),
text = "-$remaining",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelLarge,
modifier =

View file

@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@ -56,9 +57,15 @@ import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.TimeFormatter
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import org.jellyfin.sdk.model.api.MediaSegmentDto
import org.jellyfin.sdk.model.api.TrickplayInfo
import java.time.LocalTime
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
private val titleTextSize = 28.sp
private val subtitleTextSize = 18.sp
@ -74,6 +81,7 @@ fun PlaybackOverlay(
playerControls: Player,
controllerViewState: ControllerViewState,
showPlay: Boolean,
showClock: Boolean,
previousEnabled: Boolean,
nextEnabled: Boolean,
seekEnabled: Boolean,
@ -144,6 +152,7 @@ fun PlaybackOverlay(
playerControls = playerControls,
controllerViewState = controllerViewState,
showPlay = showPlay,
showClock = showClock,
previousEnabled = previousEnabled,
nextEnabled = nextEnabled,
seekEnabled = seekEnabled,
@ -415,6 +424,7 @@ fun Controller(
subtitleStreams: List<SubtitleStream>,
playerControls: Player,
controllerViewState: ControllerViewState,
showClock: Boolean,
showPlay: Boolean,
previousEnabled: Boolean,
nextEnabled: Boolean,
@ -453,12 +463,40 @@ fun Controller(
fontSize = titleTextSize,
)
}
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.titleMedium,
fontSize = subtitleTextSize,
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier =
Modifier
.fillMaxWidth()
.align(Alignment.End),
) {
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.titleMedium,
fontSize = subtitleTextSize,
)
}
if (showClock) {
var remaining by remember { mutableStateOf((playerControls.duration - playerControls.currentPosition).milliseconds) }
LaunchedEffect(playerControls) {
while (isActive) {
remaining =
(playerControls.duration - playerControls.currentPosition).milliseconds
delay(1.seconds)
}
}
val endTime = LocalTime.now().plusSeconds(remaining.inWholeSeconds)
val endTimeStr = TimeFormatter.format(endTime)
Text(
text = "Ends $endTimeStr",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelLarge,
modifier =
Modifier
.padding(end = 32.dp),
)
}
}
}
PlaybackControls(

View file

@ -387,6 +387,7 @@ fun PlaybackPage(
viewModel.playItemInPlaylist(it)
},
currentSegment = currentSegment,
showClock = preferences.appPreferences.interfacePreferences.showClock,
)
}
}

View file

@ -89,4 +89,11 @@ suspend fun upgradeApp(
}
}
}
if (previous.isEqualOrBefore(Version.fromString("0.2.5-11-g0"))) {
appPreferences.updateData {
it.updateInterfacePreferences {
showClock = AppPreference.ShowClock.defaultValue
}
}
}
}

View file

@ -18,8 +18,11 @@ import org.jellyfin.sdk.model.api.MediaStreamType
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
val TimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
/**
* Format a [LocalDateTime] as `Aug 24, 2000`
*/

View file

@ -88,6 +88,7 @@ message InterfacePreferences {
map<string, int32> remembered_tabs = 3;
AppThemeColors app_theme_colors = 4;
bool nav_drawer_switch_on_focus = 5;
bool show_clock = 6;
}
message AppPreferences {

View file

@ -138,6 +138,7 @@
<string name="watch_live">Watch live</string>
<string name="years_old">%1$d years old</string>
<string name="play_with_transcoding">Play with transcoding</string>
<string name="show_clock">Show Clock</string>
<string name="aired_episode_order">Aired Episode Order</string>
<string name="create_playlist">Create new playlist</string>
<string name="add_to_playlist">Add to playlist</string>