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:
MajorTanya
2026-10-17 22:18:32 +06:00
committed by GitHub
parent 7618df898c
commit 970b12f1bd
14 changed files with 228 additions and 40 deletions
+1
View File
@@ -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 = {},
@@ -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) {
@@ -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",
@@ -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>