mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-19 23:22:26 -07:00
Add Time Format setting (#4118)
Analogous to the Date Format setting. Offers 4 modes: - "Follow system" - based on the phone's time settings (which usually offer 12h/24h as a setting that may be toggled regardless of system language defaults) - this can be changed with an app switch which is why some callers are keyed on the 24-hour state. - "Follow language" - uses the app language's default formats - e.g. English: 12-hour, German: 24-hour, etc. - "24-hour": Forces 24-hour display - "12-hour": Forces 12-hour display
This commit is contained in:
@@ -28,6 +28,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
- Show pages progressively while they download ([@wwww-wwww](https://github.com/wwww-wwww)) ([#4023](https://github.com/mihonapp/mihon/pull/4023))
|
||||
- Lower memory use for animated images and support cropping borders in long strip ([@wwww-wwww](https://github.com/wwww-wwww)) ([#4029](https://github.com/mihonapp/mihon/pull/4029))
|
||||
- Pair more wide pages in dual page view by comparing them with neighbouring pages ([@wwww-wwww](https://github.com/wwww-wwww)) ([#4051](https://github.com/mihonapp/mihon/pull/4051))
|
||||
- Add a Time Format setting to customize how times are displayed ([@MajorTanya](https://github.com/MajorTanya)) ([#4118](https://github.com/mihonapp/mihon/pull/4118))
|
||||
|
||||
### Changed
|
||||
- Use a new decoder for images Android can't decode itself, like AVIF, JPEG XL and HEIF, and support JPEG 2000 images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#3786](https://github.com/mihonapp/mihon/pull/3786))
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package eu.kanade.domain.ui
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.domain.ui.model.AppTheme
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
import eu.kanade.domain.ui.model.ThemeMode
|
||||
import eu.kanade.domain.ui.model.TimeFormat
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
|
||||
@@ -38,14 +43,53 @@ class UiPreferences(
|
||||
|
||||
val dateFormat: Preference<String> = preferenceStore.getString("app_date_format", "")
|
||||
|
||||
val timeFormat: Preference<TimeFormat> = preferenceStore.getEnum("app_time_format", TimeFormat.FOLLOW_LANGUAGE)
|
||||
|
||||
val tabletUiMode: Preference<TabletUiMode> = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||
|
||||
val imagesInDescription: Preference<Boolean> = preferenceStore.getBoolean("pref_render_images_description", true)
|
||||
|
||||
companion object {
|
||||
fun dateFormat(format: String): DateTimeFormatter = when (format) {
|
||||
"" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||
else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
|
||||
fun dateFormat(format: String, locale: Locale): DateTimeFormatter = when (format) {
|
||||
"" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(locale)
|
||||
else -> DateTimeFormatter.ofPattern(format, locale)
|
||||
}
|
||||
|
||||
fun timeFormat(timeFormat: TimeFormat, locale: Locale, is24HourSystem: Boolean): DateTimeFormatter {
|
||||
val use24Hours = when (timeFormat) {
|
||||
TimeFormat.FOLLOW_LANGUAGE -> {
|
||||
return DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale)
|
||||
}
|
||||
|
||||
TimeFormat.FOLLOW_SYSTEM -> is24HourSystem
|
||||
TimeFormat.HOURS_24 -> true
|
||||
TimeFormat.HOURS_12 -> false
|
||||
}
|
||||
|
||||
val skeleton = if (use24Hours) "Hm" else "hm"
|
||||
val pattern = DateFormat.getBestDateTimePattern(locale, skeleton)
|
||||
return try {
|
||||
DateTimeFormatter.ofPattern(pattern, locale)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
val fallbackPattern = if (use24Hours) "HH:mm" else "h:mm a"
|
||||
logcat(LogPriority.ERROR, e) {
|
||||
"Failed to apply best pattern ('$pattern'), falling back to '$fallbackPattern'"
|
||||
}
|
||||
DateTimeFormatter.ofPattern(fallbackPattern, locale)
|
||||
}
|
||||
}
|
||||
|
||||
fun dateTimeFormat(
|
||||
dateFormat: String,
|
||||
timeFormat: TimeFormat,
|
||||
locale: Locale,
|
||||
is24HourSystem: Boolean,
|
||||
): DateTimeFormatter {
|
||||
return DateTimeFormatterBuilder()
|
||||
.append(dateFormat(dateFormat, locale))
|
||||
.appendLiteral(' ')
|
||||
.append(timeFormat(timeFormat, locale, is24HourSystem))
|
||||
.toFormatter(locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package eu.kanade.domain.ui.model
|
||||
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
enum class TimeFormat(val titleRes: StringResource) {
|
||||
FOLLOW_LANGUAGE(MR.strings.follow_language),
|
||||
FOLLOW_SYSTEM(MR.strings.follow_system),
|
||||
HOURS_24(MR.strings.time_24_hours),
|
||||
HOURS_12(MR.strings.time_12_hours),
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package eu.kanade.presentation.components
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLocale
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import kotlinx.datetime.LocalDate
|
||||
@@ -27,10 +28,11 @@ fun relativeDateText(
|
||||
localDate: LocalDate?,
|
||||
): String {
|
||||
val context = LocalContext.current
|
||||
val locale = LocalLocale.current
|
||||
|
||||
val preferences = remember { context.appGraph.uiPreferences }
|
||||
val relativeTime = remember { preferences.relativeTime.get() }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get()) }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get(), locale.platformLocale) }
|
||||
|
||||
return localDate?.toRelativeString(
|
||||
context = context,
|
||||
|
||||
@@ -6,9 +6,14 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLocale
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
@@ -16,8 +21,10 @@ import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.presentation.util.rememberIs24HourSystem
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||
import kotlinx.datetime.LocalDate
|
||||
import mihon.app.di.appGraph
|
||||
import mihon.icons.materialsymbols.MaterialSymbols
|
||||
import mihon.icons.materialsymbols.rounded.DeleteSweep
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
@@ -28,6 +35,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
@@ -99,6 +107,14 @@ private fun HistoryScreenContent(
|
||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||
onClickFavorite: (HistoryWithRelations) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val locale = LocalLocale.current
|
||||
val is24HourSystem = rememberIs24HourSystem()
|
||||
val timeFormat by remember { context.appGraph.uiPreferences.timeFormat }.collectAsState()
|
||||
val timeFormatter = remember(timeFormat, locale, is24HourSystem) {
|
||||
UiPreferences.timeFormat(timeFormat, locale.platformLocale, is24HourSystem)
|
||||
}
|
||||
|
||||
FastScrollLazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
@@ -124,6 +140,7 @@ private fun HistoryScreenContent(
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
history = value,
|
||||
timeFormatter = timeFormatter,
|
||||
onClickCover = { onClickCover(value) },
|
||||
onClickResume = { onClickResume(value) },
|
||||
onClickDelete = { onClickDelete(value) },
|
||||
|
||||
@@ -31,12 +31,15 @@ import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
private val HistoryItemHeight = 96.dp
|
||||
|
||||
@Composable
|
||||
fun HistoryItem(
|
||||
history: HistoryWithRelations,
|
||||
timeFormatter: DateTimeFormatter,
|
||||
onClickCover: () -> Unit,
|
||||
onClickResume: () -> Unit,
|
||||
onClickDelete: () -> Unit,
|
||||
@@ -68,7 +71,9 @@ fun HistoryItem(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = textStyle,
|
||||
)
|
||||
val readAt = remember { history.readAt?.toTimestampString() ?: "" }
|
||||
val readAt = remember(timeFormatter, history.readAt) {
|
||||
history.readAt?.toTimestampString(timeFormatter) ?: ""
|
||||
}
|
||||
Text(
|
||||
text = if (history.chapterNumber > -1) {
|
||||
stringResource(
|
||||
@@ -114,6 +119,7 @@ private fun HistoryItemPreviews(
|
||||
Surface {
|
||||
HistoryItem(
|
||||
history = historyWithRelations,
|
||||
timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT),
|
||||
onClickCover = {},
|
||||
onClickResume = {},
|
||||
onClickDelete = {},
|
||||
|
||||
+22
-4
@@ -7,17 +7,20 @@ import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLocale
|
||||
import androidx.core.app.ActivityCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
import eu.kanade.domain.ui.model.ThemeMode
|
||||
import eu.kanade.domain.ui.model.TimeFormat
|
||||
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||
import eu.kanade.presentation.util.rememberIs24HourSystem
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
@@ -101,12 +104,14 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val locale = LocalLocale.current
|
||||
val is24HourSystem = rememberIs24HourSystem()
|
||||
|
||||
val now = remember { Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).toJavaLocalDateTime() }
|
||||
|
||||
val dateFormat by uiPreferences.dateFormat.collectAsState()
|
||||
val formattedNow = remember(dateFormat) {
|
||||
UiPreferences.dateFormat(dateFormat).format(now)
|
||||
val formattedDateNow = remember(dateFormat) {
|
||||
UiPreferences.dateFormat(dateFormat, locale.platformLocale).format(now)
|
||||
}
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
@@ -130,18 +135,31 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
preference = uiPreferences.dateFormat,
|
||||
entries = DateFormats
|
||||
.associateWith {
|
||||
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
||||
val formattedDate = UiPreferences.dateFormat(it, locale.platformLocale).format(now)
|
||||
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
|
||||
},
|
||||
title = stringResource(MR.strings.pref_date_format),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = uiPreferences.timeFormat,
|
||||
entries = TimeFormat.entries
|
||||
.associateWith {
|
||||
val formattedTime = UiPreferences.timeFormat(
|
||||
it,
|
||||
locale.platformLocale,
|
||||
is24HourSystem,
|
||||
).format(now)
|
||||
"${stringResource(it.titleRes)} ($formattedTime)"
|
||||
},
|
||||
title = stringResource(MR.strings.pref_time_format),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = uiPreferences.relativeTime,
|
||||
title = stringResource(MR.strings.pref_relative_format),
|
||||
subtitle = stringResource(
|
||||
MR.strings.pref_relative_format_summary,
|
||||
stringResource(MR.strings.relative_time_today),
|
||||
formattedNow,
|
||||
formattedDateNow,
|
||||
),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.presentation.more.settings.screen.about
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -27,6 +28,7 @@ import eu.kanade.presentation.more.LogoHeader
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.rememberIs24HourSystem
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||
@@ -61,6 +63,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
import kotlin.time.Instant
|
||||
|
||||
object AboutScreen : Screen() {
|
||||
@@ -74,6 +77,7 @@ object AboutScreen : Screen() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
var isCheckingUpdates by remember { mutableStateOf(false) }
|
||||
val crashLogUtil = remember { context.appGraph.crashLogUtil }
|
||||
val is24HourSystem = rememberIs24HourSystem()
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
@@ -96,7 +100,9 @@ object AboutScreen : Screen() {
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.version),
|
||||
subtitle = getVersionName(withBuildDate = true),
|
||||
// Keyed on the system 12/24h setting, which can change while the app is backgrounded.
|
||||
// Date/time prefs only change in Appearance, which recreates this screen on return.
|
||||
subtitle = remember(is24HourSystem) { getVersionName(withBuildDate = true) },
|
||||
onPreferenceClick = {
|
||||
val deviceInfo = crashLogUtil.getDebugInfo()
|
||||
context.copyToClipboard("Debug information", deviceInfo)
|
||||
@@ -275,11 +281,16 @@ object AboutScreen : Screen() {
|
||||
|
||||
internal fun getFormattedBuildTime(): String {
|
||||
return try {
|
||||
val context = Injekt.get<Context>()
|
||||
val uiPreferences = context.appGraph.uiPreferences
|
||||
Instant.parse(BuildConfig.BUILD_TIME)
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.toDateTimestampString(
|
||||
UiPreferences.dateFormat(
|
||||
Injekt.get<Context>().appGraph.uiPreferences.dateFormat.get(),
|
||||
UiPreferences.dateTimeFormat(
|
||||
uiPreferences.dateFormat.get(),
|
||||
uiPreferences.timeFormat.get(),
|
||||
Locale.getDefault(),
|
||||
DateFormat.is24HourFormat(context),
|
||||
),
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
|
||||
+5
-1
@@ -18,6 +18,7 @@ import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
||||
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.rememberIs24HourSystem
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
@@ -59,6 +60,7 @@ class DebugInfoScreen : Screen() {
|
||||
private fun getAppInfoGroup(): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val is24HourSystem = rememberIs24HourSystem()
|
||||
|
||||
val installationIdPref = remember { context.appGraph.basePreferences.installationId }
|
||||
val installationId by installationIdPref.collectAsState()
|
||||
@@ -72,7 +74,9 @@ class DebugInfoScreen : Screen() {
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "Build time",
|
||||
subtitle = AboutScreen.getFormattedBuildTime(),
|
||||
// Keyed on the system 12/24h setting, which can change while the app is backgrounded.
|
||||
// Date/time prefs only change in Appearance, which recreates this screen on return.
|
||||
subtitle = remember(is24HourSystem) { AboutScreen.getFormattedBuildTime() },
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = "Installation ID",
|
||||
|
||||
+50
-18
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.presentation.more.settings.screen.debug
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -9,6 +10,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -31,11 +33,13 @@ import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.rememberIs24HourSystem
|
||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
@@ -46,6 +50,8 @@ import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlin.time.Instant
|
||||
|
||||
class WorkerInfoScreen : Screen() {
|
||||
@@ -60,6 +66,12 @@ class WorkerInfoScreen : Screen() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = metroViewModel<WorkerInfoViewModel>()
|
||||
|
||||
val is24HourSystem = rememberIs24HourSystem()
|
||||
// Keyed on the system 12/24h setting, which can change while the app is backgrounded.
|
||||
// Date/time prefs only change in Appearance, which recreates this screen on return.
|
||||
LaunchedEffect(is24HourSystem) { viewModel.onSystemTimeFormatChanged(is24HourSystem) }
|
||||
|
||||
val enqueued by viewModel.enqueued.collectAsState()
|
||||
val finished by viewModel.finished.collectAsState()
|
||||
val running by viewModel.running.collectAsState()
|
||||
@@ -124,29 +136,53 @@ class WorkerInfoScreen : Screen() {
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
class WorkerInfoViewModel(
|
||||
private val context: Context,
|
||||
context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
private val workManager = context.workManager
|
||||
private val dateFormatPref = context.appGraph.uiPreferences.dateFormat
|
||||
private val timeFormatPref = context.appGraph.uiPreferences.timeFormat
|
||||
private val is24HourSystem = MutableStateFlow(DateFormat.is24HourFormat(context))
|
||||
|
||||
val finished = workManager
|
||||
.getWorkInfosFlow(
|
||||
private val dateTimeFormatter = combine(
|
||||
dateFormatPref.changes(),
|
||||
timeFormatPref.changes(),
|
||||
is24HourSystem,
|
||||
) { date, time, is24 ->
|
||||
UiPreferences.dateTimeFormat(date, time, Locale.getDefault(), is24)
|
||||
}
|
||||
|
||||
fun onSystemTimeFormatChanged(value: Boolean) {
|
||||
is24HourSystem.value = value
|
||||
}
|
||||
|
||||
val finished = combine(
|
||||
workManager.getWorkInfosFlow(
|
||||
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
||||
)
|
||||
.map(::constructString)
|
||||
),
|
||||
dateTimeFormatter,
|
||||
::constructString,
|
||||
)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
val running = workManager
|
||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||
.map(::constructString)
|
||||
val running = combine(
|
||||
workManager.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING)),
|
||||
dateTimeFormatter,
|
||||
::constructString,
|
||||
)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
val enqueued = workManager
|
||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
||||
.map(::constructString)
|
||||
val enqueued = combine(
|
||||
workManager.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED)),
|
||||
dateTimeFormatter,
|
||||
::constructString,
|
||||
)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
private fun constructString(list: List<WorkInfo>) = buildString {
|
||||
private fun constructString(
|
||||
list: List<WorkInfo>,
|
||||
dateTimeFormatter: DateTimeFormatter,
|
||||
) = buildString {
|
||||
if (list.isEmpty()) {
|
||||
appendLine("-")
|
||||
} else {
|
||||
@@ -160,11 +196,7 @@ class WorkerInfoScreen : Screen() {
|
||||
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
val timestamp = Instant.fromEpochMilliseconds(workInfo.nextScheduleTimeMillis)
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.toDateTimestampString(
|
||||
UiPreferences.dateFormat(
|
||||
context.appGraph.uiPreferences.dateFormat.get(),
|
||||
),
|
||||
)
|
||||
.toDateTimestampString(dateTimeFormatter)
|
||||
appendLine("Next scheduled run: $timestamp")
|
||||
appendLine("Attempt #${workInfo.runAttemptCount + 1}")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package eu.kanade.presentation.util
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
|
||||
@Composable
|
||||
fun rememberIs24HourSystem(): Boolean {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val context = LocalContext.current
|
||||
var is24 by remember { mutableStateOf(DateFormat.is24HourFormat(context)) }
|
||||
DisposableEffect(lifecycleOwner.lifecycle) {
|
||||
val observer = object : DefaultLifecycleObserver {
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
is24 = DateFormat.is24HourFormat(context)
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
return is24
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLocale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -104,9 +105,15 @@ data class TrackInfoDialogHomeScreen(
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val locale = LocalLocale.current
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(mangaId = mangaId, sourceId = sourceId) }
|
||||
|
||||
val dateFormat = remember { UiPreferences.dateFormat(context.appGraph.uiPreferences.dateFormat.get()) }
|
||||
val dateFormat = remember {
|
||||
UiPreferences.dateFormat(
|
||||
context.appGraph.uiPreferences.dateFormat.get(),
|
||||
locale.platformLocale,
|
||||
)
|
||||
}
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
TrackInfoDialogHome(
|
||||
|
||||
@@ -13,21 +13,17 @@ import tachiyomi.core.common.i18n.pluralStringResource
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
|
||||
val javaLocalDateTime = this.toJavaLocalDateTime()
|
||||
val date = dateTimeFormatter.format(javaLocalDateTime)
|
||||
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(javaLocalDateTime)
|
||||
return "$date $time"
|
||||
return dateTimeFormatter.format(javaLocalDateTime)
|
||||
}
|
||||
|
||||
fun Instant.toTimestampString(): String {
|
||||
return DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
.format(toLocalDateTime(TimeZone.currentSystemDefault()).toJavaLocalDateTime())
|
||||
fun Instant.toTimestampString(timeFormatter: DateTimeFormatter): String {
|
||||
return timeFormatter.format(toLocalDateTime(TimeZone.currentSystemDefault()).toJavaLocalDateTime())
|
||||
}
|
||||
|
||||
fun Long.convertEpochMillisZone(
|
||||
@@ -55,7 +51,7 @@ fun Instant.toJavaLocalDate(): java.time.LocalDate {
|
||||
fun LocalDate.toRelativeString(
|
||||
context: Context,
|
||||
relative: Boolean = true,
|
||||
dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
|
||||
dateFormat: DateTimeFormatter,
|
||||
): String {
|
||||
if (!relative) {
|
||||
return dateFormat.format(this.toJavaLocalDate())
|
||||
|
||||
@@ -250,6 +250,13 @@
|
||||
<!-- "Today" instead of "2023-12-31" -->
|
||||
<string name="pref_relative_format_summary">\"%1$s\" instead of \"%2$s\"</string>
|
||||
<string name="pref_date_format">Date format</string>
|
||||
<string name="pref_time_format">Time format</string>
|
||||
<!-- "Follow" as in "adhere to", "align with" (the system default) -->
|
||||
<string name="follow_system">Follow system</string>
|
||||
<!-- "Follow" as in "adhere to", "align with" (the language default) -->
|
||||
<string name="follow_language">Follow language</string>
|
||||
<string name="time_24_hours">24-hour</string>
|
||||
<string name="time_12_hours">12-hour</string>
|
||||
<string name="pref_display_images_description">Render images in manga descriptions</string>
|
||||
|
||||
<string name="pref_manage_notifications">Manage notifications</string>
|
||||
|
||||
Reference in New Issue
Block a user