Replace java.time APIs with kotlin(x).(date)time (#3001)
* Replace java.time APIs with kotlin(x).(date)time kotlinx-datetime explicitly says in its README that it does not cover i18n, so we have to stick with using their toJavaX converters wherever we localise dates and times. Yes, some of these replacements are... questionable. But I wanted to try and maximise the replacement for now. * Bump kotlinx-datetime to 0.8.0 * More kotlin.time/kotlinx-datetime replacements * Remove redundant init block & make attributes val * Replace new use of java.time.Instant
This commit is contained in:
@@ -243,6 +243,8 @@ dependencies {
|
||||
|
||||
implementation(libs.sqldelight.async)
|
||||
|
||||
implementation(libs.kotlinx.datetime)
|
||||
|
||||
// AndroidX libraries
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appCompat)
|
||||
|
||||
Vendored
+3
@@ -86,3 +86,6 @@
|
||||
# Firebase
|
||||
-keep class com.google.firebase.installations.** { *; }
|
||||
-keep interface com.google.firebase.installations.** { *; }
|
||||
|
||||
# KotlinX Datetime
|
||||
-keep,allowoptimization class kotlinx.datetime.** { public protected *; }
|
||||
|
||||
@@ -10,6 +10,9 @@ import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.data.chapter.ChapterSanitizer
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
@@ -23,8 +26,8 @@ import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.lang.Long.max
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.TreeSet
|
||||
import kotlin.time.Clock
|
||||
|
||||
class SyncChaptersWithSource(
|
||||
private val downloadManager: DownloadManager,
|
||||
@@ -57,8 +60,9 @@ class SyncChaptersWithSource(
|
||||
throw NoChaptersException()
|
||||
}
|
||||
|
||||
val now = ZonedDateTime.now()
|
||||
val nowMillis = now.toInstant().toEpochMilli()
|
||||
val timeZone = TimeZone.currentSystemDefault()
|
||||
val now = Clock.System.now().toLocalDateTime(timeZone)
|
||||
val nowMillis = now.toInstant(timeZone).toEpochMilliseconds()
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
@@ -145,6 +149,7 @@ class SyncChaptersWithSource(
|
||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||
updateManga.awaitUpdateFetchInterval(
|
||||
manga,
|
||||
timeZone,
|
||||
now,
|
||||
fetchWindow,
|
||||
)
|
||||
@@ -217,7 +222,7 @@ class SyncChaptersWithSource(
|
||||
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||
updateManga.awaitUpdateFetchInterval(manga, timeZone, now, fetchWindow)
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.time.Clock
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
@@ -22,25 +24,31 @@ class UpdateManga(
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||
timeZone: TimeZone = TimeZone.currentSystemDefault(),
|
||||
dateTime: LocalDateTime = Clock.System.now().toLocalDateTime(timeZone),
|
||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime.date, timeZone),
|
||||
): Boolean {
|
||||
return mangaRepository.update(
|
||||
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
||||
fetchInterval.toMangaUpdate(manga, dateTime, timeZone, window),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Instant.now().toEpochMilli()))
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Clock.System.now().toEpochMilliseconds()))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Instant.now().toEpochMilli()))
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = mangaId,
|
||||
coverLastModified = Clock.System.now().toEpochMilliseconds(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||
val dateAdded = when (favorite) {
|
||||
true -> Instant.now().toEpochMilli()
|
||||
true -> Clock.System.now().toEpochMilliseconds()
|
||||
false -> 0
|
||||
}
|
||||
return mangaRepository.update(
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
import kotlinx.datetime.TimeZone
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||
@@ -18,7 +19,6 @@ import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class AddTracks(
|
||||
private val insertTrack: InsertTrack,
|
||||
@@ -62,8 +62,8 @@ class AddTracks(
|
||||
|
||||
firstReadChapterDate?.let {
|
||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||
ZoneOffset.systemDefault(),
|
||||
ZoneOffset.UTC,
|
||||
TimeZone.currentSystemDefault(),
|
||||
TimeZone.UTC,
|
||||
)
|
||||
track = track.copy(
|
||||
startDate = startDate,
|
||||
|
||||
@@ -5,23 +5,23 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Composable
|
||||
fun relativeDateText(
|
||||
dateEpochMillis: Long,
|
||||
): String {
|
||||
return relativeDateText(
|
||||
localDate = LocalDate.ofInstant(
|
||||
Instant.ofEpochMilli(dateEpochMillis),
|
||||
ZoneId.systemDefault(),
|
||||
)
|
||||
localDate = Instant.fromEpochMilliseconds(dateEpochMillis)
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.date
|
||||
.takeIf { dateEpochMillis != 0L },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||
import kotlinx.datetime.LocalDate
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
@@ -27,7 +28,6 @@ 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 java.time.LocalDate
|
||||
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
|
||||
@@ -2,13 +2,16 @@ package eu.kanade.presentation.history
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Instant
|
||||
import kotlin.time.toJavaInstant
|
||||
|
||||
class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.State> {
|
||||
|
||||
@@ -18,9 +21,9 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
||||
listOf(HistoryUiModelExamples.headerToday)
|
||||
.asSequence()
|
||||
.plus(HistoryUiModelExamples.items().take(3))
|
||||
.plus(HistoryUiModelExamples.header { it.minus(1, ChronoUnit.DAYS) })
|
||||
.plus(HistoryUiModelExamples.header { it.minus(1.days) })
|
||||
.plus(HistoryUiModelExamples.items().take(1))
|
||||
.plus(HistoryUiModelExamples.header { it.minus(2, ChronoUnit.DAYS) })
|
||||
.plus(HistoryUiModelExamples.header { it.minus(2.days) })
|
||||
.plus(HistoryUiModelExamples.items().take(7))
|
||||
.toList(),
|
||||
dialog = null,
|
||||
@@ -72,10 +75,12 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
||||
private object HistoryUiModelExamples {
|
||||
val headerToday = header()
|
||||
val headerTomorrow =
|
||||
HistoryUiModel.Header(LocalDate.now().plusDays(1))
|
||||
HistoryUiModel.Header(Clock.System.now().plus(1.days).toLocalDateTime(TimeZone.currentSystemDefault()).date)
|
||||
|
||||
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
||||
HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
|
||||
HistoryUiModel.Header(
|
||||
instantBuilder(Clock.System.now()).toLocalDateTime(TimeZone.currentSystemDefault()).date,
|
||||
)
|
||||
|
||||
fun items() = sequence {
|
||||
var count = 1
|
||||
@@ -94,7 +99,7 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
||||
mangaId = Random.nextLong(),
|
||||
title = "Test Title",
|
||||
chapterNumber = Random.nextDouble(),
|
||||
readAt = Date.from(Instant.now()),
|
||||
readAt = Date.from(Clock.System.now().toJavaInstant()),
|
||||
readDuration = Random.nextLong(),
|
||||
coverData = MangaCover(
|
||||
mangaId = Random.nextLong(),
|
||||
|
||||
@@ -75,7 +75,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.time.Instant
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Composable
|
||||
fun MangaScreen(
|
||||
|
||||
@@ -20,15 +20,17 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.daysUntil
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Composable
|
||||
fun DeleteChaptersDialog(
|
||||
@@ -72,8 +74,8 @@ fun SetIntervalDialog(
|
||||
|
||||
val nextUpdateDays = remember(nextUpdate) {
|
||||
return@remember if (nextUpdate != null) {
|
||||
val now = Instant.now()
|
||||
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
||||
val now = Clock.System.now()
|
||||
now.daysUntil(nextUpdate, TimeZone.currentSystemDefault()).coerceAtLeast(0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.daysUntil
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.ast.findChildOfType
|
||||
@@ -105,9 +107,9 @@ import tachiyomi.presentation.core.util.clickableNoIndication
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Composable
|
||||
fun MangaInfoBox(
|
||||
@@ -189,8 +191,8 @@ fun MangaActionRow(
|
||||
// TODO: show something better when using custom interval
|
||||
val nextUpdateDays = remember(nextUpdate) {
|
||||
return@remember if (nextUpdate != null) {
|
||||
val now = Instant.now()
|
||||
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
||||
val now = Clock.System.now()
|
||||
now.daysUntil(nextUpdate, TimeZone.currentSystemDefault()).coerceAtLeast(0)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
+5
-2
@@ -19,12 +19,15 @@ 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.tachiyomi.util.system.toast
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.LocalDate
|
||||
import kotlin.time.Clock
|
||||
|
||||
object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
@@ -99,7 +102,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val now = remember { LocalDate.now() }
|
||||
val now = remember { Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).toJavaLocalDateTime() }
|
||||
|
||||
val dateFormat by uiPreferences.dateFormat.collectAsState()
|
||||
val formattedNow = remember(dateFormat) {
|
||||
|
||||
@@ -40,6 +40,8 @@ import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.system.updaterEnabled
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.Constants
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
@@ -59,9 +61,7 @@ import tachiyomi.presentation.core.icons.Reddit
|
||||
import tachiyomi.presentation.core.icons.X
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.time.Instant
|
||||
|
||||
object AboutScreen : Screen() {
|
||||
|
||||
@@ -273,16 +273,14 @@ object AboutScreen : Screen() {
|
||||
|
||||
internal fun getFormattedBuildTime(): String {
|
||||
return try {
|
||||
LocalDateTime.ofInstant(
|
||||
Instant.parse(BuildConfig.BUILD_TIME),
|
||||
ZoneId.systemDefault(),
|
||||
)
|
||||
Instant.parse(BuildConfig.BUILD_TIME)
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.toDateTimestampString(
|
||||
UiPreferences.dateFormat(
|
||||
Injekt.get<UiPreferences>().dateFormat.get(),
|
||||
),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
BuildConfig.BUILD_TIME
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -38,15 +38,15 @@ import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.time.Instant
|
||||
|
||||
class WorkerInfoScreen : Screen() {
|
||||
|
||||
@@ -165,10 +165,8 @@ class WorkerInfoScreen : Screen() {
|
||||
}
|
||||
appendLine("State: ${workInfo.state}")
|
||||
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
val timestamp = LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis),
|
||||
ZoneId.systemDefault(),
|
||||
)
|
||||
val timestamp = Instant.fromEpochMilliseconds(workInfo.nextScheduleTimeMillis)
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.toDateTimestampString(
|
||||
UiPreferences.dateFormat(
|
||||
Injekt.get<UiPreferences>().dateFormat.get(),
|
||||
|
||||
@@ -56,8 +56,10 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||
import eu.kanade.tachiyomi.util.lang.toJavaLocalDate
|
||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import kotlinx.datetime.toJavaLocalDate
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.format.DateTimeFormatter
|
||||
@@ -110,11 +112,13 @@ fun TrackInfoDialogHome(
|
||||
.takeIf { supportsScoring && item.track.score != 0.0 },
|
||||
onScoreClick = { onScoreClick(item) }
|
||||
.takeIf { supportsScoring },
|
||||
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate.toLocalDate()) }
|
||||
startDate = remember(item.track.startDate) {
|
||||
dateFormat.format(item.track.startDate.toLocalDate().toJavaLocalDate())
|
||||
}
|
||||
.takeIf { supportsReadingDates && item.track.startDate != 0L },
|
||||
onStartDateClick = { onStartDateEdit(item) } // TODO
|
||||
.takeIf { supportsReadingDates },
|
||||
endDate = dateFormat.format(item.track.finishDate.toLocalDate())
|
||||
endDate = dateFormat.format(item.track.finishDate.toJavaLocalDate())
|
||||
.takeIf { supportsReadingDates && item.track.finishDate != 0L },
|
||||
onEndDateClick = { onEndDateEdit(item) }
|
||||
.takeIf { supportsReadingDates },
|
||||
|
||||
@@ -6,11 +6,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.toJavaInstant
|
||||
|
||||
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||
private val fullPageWithSecondSelected = @Composable {
|
||||
@@ -93,7 +94,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
||||
it.finished_reading_date = 0L
|
||||
it.tracking_url = "https://example.com/tracker-example"
|
||||
it.cover_url = "https://example.com/cover.png"
|
||||
it.start_date = formatter.format(Date.from(Instant.now().minus((1L..365).random(), ChronoUnit.DAYS)))
|
||||
it.start_date = formatter.format(Date.from(Clock.System.now().minus((1L..365).random().days).toJavaInstant()))
|
||||
it.summary = lorem((0..40).random()).joinToString()
|
||||
it.publishing_status = if (Random.nextBoolean()) "Finished" else ""
|
||||
it.publishing_type = if (Random.nextBoolean()) "Oneshot" else ""
|
||||
|
||||
@@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
@@ -40,7 +41,6 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.theme.active
|
||||
import java.time.LocalDate
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.compose.runtime.ReadOnlyComposable
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@@ -29,7 +29,7 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun relativeTimeSpanString(epochMillis: Long): String {
|
||||
val now = Instant.now().toEpochMilli()
|
||||
val now = Clock.System.now().toEpochMilliseconds()
|
||||
return when {
|
||||
epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never)
|
||||
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(
|
||||
|
||||
@@ -18,7 +18,7 @@ import tachiyomi.core.common.storage.displayablePath
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class BackupNotifier(private val context: Context) {
|
||||
|
||||
@@ -149,10 +149,8 @@ class BackupNotifier(private val context: Context) {
|
||||
|
||||
val timeString = context.stringResource(
|
||||
MR.strings.restore_duration,
|
||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
||||
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
|
||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
||||
),
|
||||
time.milliseconds.inWholeMinutes,
|
||||
time.milliseconds.inWholeSeconds - (time.milliseconds.inWholeMinutes * 60),
|
||||
)
|
||||
|
||||
with(completeNotificationBuilder) {
|
||||
|
||||
@@ -33,9 +33,9 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.FileOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.time.Clock
|
||||
|
||||
class BackupCreator(
|
||||
private val context: Context,
|
||||
@@ -108,7 +108,7 @@ class BackupCreator(
|
||||
BackupFileValidator(context).validate(fileUri)
|
||||
|
||||
if (isAutoBackup) {
|
||||
backupPreferences.lastAutoBackupTimestamp.set(Instant.now().toEpochMilli())
|
||||
backupPreferences.lastAutoBackupTimestamp.set(Clock.System.now().toEpochMilliseconds())
|
||||
}
|
||||
|
||||
return fileUri.toString()
|
||||
|
||||
+7
-9
@@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupChapter
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.data.Database
|
||||
import tachiyomi.data.MemoColumnAdapter
|
||||
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||
@@ -23,9 +25,9 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Clock
|
||||
|
||||
class MangaRestorer(
|
||||
private val database: Database = Injekt.get(),
|
||||
@@ -38,13 +40,9 @@ class MangaRestorer(
|
||||
fetchInterval: FetchInterval = Injekt.get(),
|
||||
) {
|
||||
|
||||
private var now = ZonedDateTime.now()
|
||||
private var currentFetchWindow = fetchInterval.getWindow(now)
|
||||
|
||||
init {
|
||||
now = ZonedDateTime.now()
|
||||
currentFetchWindow = fetchInterval.getWindow(now)
|
||||
}
|
||||
private val timeZone = TimeZone.currentSystemDefault()
|
||||
private val now = Clock.System.now().toLocalDateTime(timeZone)
|
||||
private val currentFetchWindow = fetchInterval.getWindow(now.date, timeZone)
|
||||
|
||||
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
|
||||
val urlsBySource = database.mangasQueries
|
||||
@@ -286,7 +284,7 @@ class MangaRestorer(
|
||||
restoreTracking(manga, tracks)
|
||||
restoreHistory(manga, history)
|
||||
restoreExcludedScanlators(manga, excludedScanlators)
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
|
||||
updateManga.awaitUpdateFetchInterval(manga, timeZone, now, currentFetchWindow)
|
||||
return manga
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
@@ -64,14 +66,13 @@ import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.concurrent.atomics.incrementAndFetch
|
||||
import kotlin.time.Clock
|
||||
|
||||
@OptIn(ExperimentalAtomicApi::class)
|
||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
@@ -108,7 +109,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
|
||||
setForegroundSafely()
|
||||
|
||||
libraryPreferences.lastUpdatedTimestamp.set(Instant.now().toEpochMilli())
|
||||
libraryPreferences.lastUpdatedTimestamp.set(Clock.System.now().toEpochMilliseconds())
|
||||
|
||||
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
||||
addMangaToQueue(categoryId)
|
||||
@@ -167,7 +168,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
|
||||
val restrictions = libraryPreferences.autoUpdateMangaRestrictions.get()
|
||||
val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
|
||||
val (_, fetchWindowUpperBound) = fetchInterval.getWindow(ZonedDateTime.now())
|
||||
val timeZone = TimeZone.currentSystemDefault()
|
||||
val (_, fetchWindowUpperBound) = fetchInterval.getWindow(
|
||||
Clock.System.now().toLocalDateTime(timeZone).date,
|
||||
timeZone,
|
||||
)
|
||||
|
||||
mangaToUpdate = listToUpdate
|
||||
.filter {
|
||||
@@ -234,7 +239,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||
val hasDownloads = AtomicBoolean(false)
|
||||
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now())
|
||||
val timeZone = TimeZone.currentSystemDefault()
|
||||
val fetchWindow = fetchInterval.getWindow(Clock.System.now().toLocalDateTime(timeZone).date, timeZone)
|
||||
|
||||
coroutineScope {
|
||||
mangaToUpdate.groupBy { it.manga.source }.values
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
|
||||
class ImageSaver(
|
||||
val context: Context,
|
||||
@@ -85,7 +85,7 @@ class ImageSaver(
|
||||
MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
|
||||
MediaStore.MediaColumns.MIME_TYPE to type.mime,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED to Clock.System.now().epochSeconds,
|
||||
)
|
||||
|
||||
val picture = findUriOrDefault(relativePath, filename) {
|
||||
|
||||
@@ -15,6 +15,9 @@ import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.network.jsonMime
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.number
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -25,10 +28,8 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Instant
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
@@ -326,11 +327,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
}
|
||||
|
||||
val dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateValue), ZoneId.systemDefault())
|
||||
val dateTime = Instant.fromEpochMilliseconds(dateValue).toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
return buildJsonObject {
|
||||
put("year", dateTime.year)
|
||||
put("month", dateTime.monthValue)
|
||||
put("day", dateTime.dayOfMonth)
|
||||
put("month", dateTime.month.number)
|
||||
put("day", dateTime.day)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package eu.kanade.tachiyomi.data.track.anilist.dto
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.atStartOfDayIn
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
@Serializable
|
||||
data class ALFuzzyDate(
|
||||
@@ -11,10 +12,9 @@ data class ALFuzzyDate(
|
||||
val day: Int?,
|
||||
) {
|
||||
fun toEpochMilli(): Long = try {
|
||||
LocalDate.of(year!!, month!!, day!!)
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
LocalDate(year!!, month!!, day!!)
|
||||
.atStartOfDayIn(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||
|
||||
class BrowseSourceViewModel(
|
||||
@@ -226,7 +226,7 @@ class BrowseSourceViewModel(
|
||||
favorite = !manga.favorite,
|
||||
dateAdded = when (manga.favorite) {
|
||||
true -> 0
|
||||
false -> Instant.now().toEpochMilli()
|
||||
false -> Clock.System.now().toEpochMilliseconds()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@@ -66,6 +65,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import logcat.LogPriority
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
@@ -85,9 +86,8 @@ import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
data class TrackInfoDialogHomeScreen(
|
||||
private val mangaId: Long,
|
||||
@@ -513,20 +513,20 @@ private data class TrackDateSelectorScreen(
|
||||
@Transient
|
||||
private val selectableDates = object : SelectableDates {
|
||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||
val targetDate = Instant.ofEpochMilli(utcTimeMillis).toLocalDate(ZoneOffset.UTC)
|
||||
val targetDate = Instant.fromEpochMilliseconds(utcTimeMillis).toLocalDateTime(TimeZone.UTC)
|
||||
|
||||
// Disallow future dates
|
||||
if (targetDate > LocalDate.now(ZoneOffset.UTC)) return false
|
||||
if (targetDate > Clock.System.now().toLocalDateTime(TimeZone.UTC)) return false
|
||||
|
||||
return when {
|
||||
// Disallow setting start date after finish date
|
||||
start && track.finishDate > 0 -> {
|
||||
val finishDate = Instant.ofEpochMilli(track.finishDate).toLocalDate(ZoneOffset.UTC)
|
||||
val finishDate = Instant.fromEpochMilliseconds(track.finishDate).toLocalDateTime(TimeZone.UTC)
|
||||
targetDate <= finishDate
|
||||
}
|
||||
// Disallow setting finish date before start date
|
||||
!start && track.startDate > 0 -> {
|
||||
val startDate = Instant.ofEpochMilli(track.startDate).toLocalDate(ZoneOffset.UTC)
|
||||
val startDate = Instant.fromEpochMilliseconds(track.startDate).toLocalDateTime(TimeZone.UTC)
|
||||
startDate <= targetDate
|
||||
}
|
||||
else -> {
|
||||
@@ -537,17 +537,17 @@ private data class TrackDateSelectorScreen(
|
||||
|
||||
override fun isSelectableYear(year: Int): Boolean {
|
||||
// Disallow future years
|
||||
if (year > LocalDate.now(ZoneOffset.UTC).year) return false
|
||||
if (year > Clock.System.now().toLocalDateTime(TimeZone.UTC).year) return false
|
||||
|
||||
return when {
|
||||
// Disallow setting start year after finish year
|
||||
start && track.finishDate > 0 -> {
|
||||
val finishDate = Instant.ofEpochMilli(track.finishDate).toLocalDate(ZoneOffset.UTC)
|
||||
val finishDate = Instant.fromEpochMilliseconds(track.finishDate).toLocalDateTime(TimeZone.UTC)
|
||||
year <= finishDate.year
|
||||
}
|
||||
// Disallow setting finish year before start year
|
||||
!start && track.startDate > 0 -> {
|
||||
val startDate = Instant.ofEpochMilli(track.startDate).toLocalDate(ZoneOffset.UTC)
|
||||
val startDate = Instant.fromEpochMilliseconds(track.startDate).toLocalDateTime(TimeZone.UTC)
|
||||
startDate.year <= year
|
||||
}
|
||||
else -> {
|
||||
@@ -618,14 +618,14 @@ private data class TrackDateSelectorScreen(
|
||||
get() {
|
||||
val millis = (if (start) track.startDate else track.finishDate)
|
||||
.takeIf { it != 0L }
|
||||
?: Instant.now().toEpochMilli()
|
||||
return millis.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
||||
?: Clock.System.now().toEpochMilliseconds()
|
||||
return millis.convertEpochMillisZone(TimeZone.currentSystemDefault(), TimeZone.UTC)
|
||||
}
|
||||
|
||||
// In UTC
|
||||
fun setDate(millis: Long) {
|
||||
// Convert to local time
|
||||
val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
|
||||
val localMillis = millis.convertEpochMillisZone(TimeZone.UTC, TimeZone.currentSystemDefault())
|
||||
viewModelScope.launchNonCancellable {
|
||||
if (start) {
|
||||
tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
|
||||
|
||||
@@ -76,8 +76,9 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
import kotlin.getValue
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Presenter used by the activity to perform background operations.
|
||||
@@ -582,7 +583,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun restartReadTimer() {
|
||||
chapterReadStartTime = Instant.now().toEpochMilli()
|
||||
chapterReadStartTime = Clock.System.now().toEpochMilliseconds()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,9 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.DateTimeUnit
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.minus
|
||||
import logcat.LogPriority
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
@@ -49,7 +52,7 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.time.Clock
|
||||
|
||||
class UpdatesViewModel(
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
@@ -77,7 +80,7 @@ class UpdatesViewModel(
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
// Set date limit for recent chapters
|
||||
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
||||
val limit = Clock.System.now().minus(3, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())
|
||||
|
||||
combine(
|
||||
// needed for SQL filters (unread, started, bookmarked, etc)
|
||||
|
||||
@@ -10,12 +10,14 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.offsetAt
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.time.Clock
|
||||
|
||||
class CrashLogUtil(
|
||||
private val context: Context,
|
||||
@@ -35,12 +37,14 @@ class CrashLogUtil(
|
||||
|
||||
val uri = file.getUriCompat(context)
|
||||
context.startActivity(uri.toShareIntent(context, "text/plain"))
|
||||
} catch (e: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
withUIContext { context.toast("Failed to get logs") }
|
||||
}
|
||||
}
|
||||
|
||||
fun getDebugInfo(): String {
|
||||
val now = Clock.System.now()
|
||||
val tz = TimeZone.currentSystemDefault()
|
||||
return """
|
||||
App ID: ${BuildConfig.APPLICATION_ID}
|
||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME})
|
||||
@@ -51,7 +55,7 @@ class CrashLogUtil(
|
||||
Device name: ${Build.DEVICE} (${Build.PRODUCT})
|
||||
Device model: ${Build.MODEL}
|
||||
WebView: ${WebViewUtil.getVersion(context)}
|
||||
Current time: ${OffsetDateTime.now(ZoneId.systemDefault())}
|
||||
Current time: ${now.toLocalDateTime(tz)}${tz.offsetAt(now)}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.InputStream
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
|
||||
fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
|
||||
if (isLocal()) return this
|
||||
return if (coverCache.deleteFromCache(this, true) > 0) {
|
||||
copy(coverLastModified = Instant.now().toEpochMilli())
|
||||
copy(coverLastModified = Clock.System.now().toEpochMilliseconds())
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package eu.kanade.tachiyomi.util.lang
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.minus
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toJavaLocalDate
|
||||
import kotlinx.datetime.toJavaLocalDateTime
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.core.common.i18n.pluralStringResource
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import java.text.DateFormat
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Date
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
|
||||
val date = dateTimeFormatter.format(this)
|
||||
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(this)
|
||||
val javaLocalDateTime = this.toJavaLocalDateTime()
|
||||
val date = dateTimeFormatter.format(javaLocalDateTime)
|
||||
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(javaLocalDateTime)
|
||||
return "$date $time"
|
||||
}
|
||||
|
||||
@@ -26,21 +32,21 @@ fun Date.toTimestampString(): String {
|
||||
}
|
||||
|
||||
fun Long.convertEpochMillisZone(
|
||||
from: ZoneId,
|
||||
to: ZoneId,
|
||||
from: TimeZone,
|
||||
to: TimeZone,
|
||||
): Long {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), from)
|
||||
.atZone(to)
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
return Instant.fromEpochMilliseconds(this)
|
||||
.toLocalDateTime(from)
|
||||
.toInstant(to)
|
||||
.toEpochMilliseconds()
|
||||
}
|
||||
|
||||
fun Long.toLocalDate(): LocalDate {
|
||||
return LocalDate.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
|
||||
return Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
}
|
||||
|
||||
fun Instant.toLocalDate(zoneId: ZoneId = ZoneId.systemDefault()): LocalDate {
|
||||
return LocalDate.ofInstant(this, zoneId)
|
||||
fun Long.toJavaLocalDate(): java.time.LocalDate {
|
||||
return this.toLocalDate().toJavaLocalDate()
|
||||
}
|
||||
|
||||
fun LocalDate.toRelativeString(
|
||||
@@ -49,23 +55,25 @@ fun LocalDate.toRelativeString(
|
||||
dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
|
||||
): String {
|
||||
if (!relative) {
|
||||
return dateFormat.format(this)
|
||||
return dateFormat.format(this.toJavaLocalDate())
|
||||
}
|
||||
val now = LocalDate.now()
|
||||
val difference = ChronoUnit.DAYS.between(this, now)
|
||||
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
val difference = (today - this).days
|
||||
return when {
|
||||
difference < -7 -> dateFormat.format(this)
|
||||
difference < -7 -> dateFormat.format(this.toJavaLocalDate())
|
||||
difference < 0 -> context.pluralStringResource(
|
||||
MR.plurals.upcoming_relative_time,
|
||||
difference.toInt().absoluteValue,
|
||||
difference.toInt().absoluteValue,
|
||||
difference.absoluteValue,
|
||||
difference.absoluteValue,
|
||||
)
|
||||
|
||||
difference < 1 -> context.stringResource(MR.strings.relative_time_today)
|
||||
difference < 7 -> context.pluralStringResource(
|
||||
MR.plurals.relative_time,
|
||||
difference.toInt(),
|
||||
difference.toInt(),
|
||||
difference,
|
||||
difference,
|
||||
)
|
||||
else -> dateFormat.format(this)
|
||||
|
||||
else -> dateFormat.format(this.toJavaLocalDate())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
|
||||
class MigrateMangaUseCase(
|
||||
private val sourcePreferences: SourcePreferences,
|
||||
@@ -124,7 +124,7 @@ class MigrateMangaUseCase(
|
||||
favorite = true,
|
||||
chapterFlags = current.chapterFlags,
|
||||
viewerFlags = current.viewerFlags,
|
||||
dateAdded = if (replace) current.dateAdded else Instant.now().toEpochMilli(),
|
||||
dateAdded = if (replace) current.dateAdded else Clock.System.now().toEpochMilliseconds(),
|
||||
notes = if (MigrationFlag.NOTES in flags) current.notes else null,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.time.Instant
|
||||
import kotlin.time.Clock
|
||||
|
||||
class UpdateMangaFromRemote(
|
||||
private val sourceManager: SourceManager,
|
||||
@@ -107,14 +107,14 @@ class UpdateMangaFromRemote(
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.isLocal() -> Clock.System.now().toEpochMilliseconds()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Instant.now().toEpochMilli()
|
||||
Clock.System.now().toEpochMilliseconds()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package mihon.feature.library
|
||||
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import mihon.domain.library.model.search.AndNode
|
||||
import mihon.domain.library.model.search.ComparisonField
|
||||
import mihon.domain.library.model.search.ComparisonQueryNode
|
||||
@@ -12,10 +15,8 @@ import mihon.domain.library.model.search.NotNode
|
||||
import mihon.domain.library.model.search.OrNode
|
||||
import mihon.domain.library.model.search.QueryNode
|
||||
import tachiyomi.source.local.LocalSource
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import kotlin.math.abs
|
||||
import kotlin.time.Instant
|
||||
|
||||
fun QueryNode.matches(item: LibraryItem): Boolean {
|
||||
return when (this) {
|
||||
@@ -109,7 +110,7 @@ private fun ComparisonQueryNode.matches(item: LibraryItem): Boolean {
|
||||
|
||||
fun compareDates(timestamp: Long, value: String): Boolean? {
|
||||
val inputDate = runCatching { LocalDate.parse(value) }.getOrNull() ?: return null
|
||||
val mangaDate = Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate()
|
||||
val mangaDate = Instant.fromEpochMilliseconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
return queryComparator.apply(mangaDate, inputDate)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.relativeDateText
|
||||
import eu.kanade.presentation.util.isTabletUi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.YearMonth
|
||||
import mihon.feature.upcoming.components.UpcomingItem
|
||||
import mihon.feature.upcoming.components.calendar.Calendar
|
||||
import tachiyomi.core.common.Constants
|
||||
@@ -36,8 +38,6 @@ import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.LocalDate
|
||||
import java.time.YearMonth
|
||||
|
||||
@Composable
|
||||
fun UpcomingScreenContent(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mihon.feature.upcoming
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import java.time.LocalDate
|
||||
|
||||
sealed interface UpcomingUIModel {
|
||||
data class Header(val date: LocalDate, val mangaCount: Int) : UpcomingUIModel
|
||||
|
||||
@@ -4,17 +4,20 @@ import androidx.compose.ui.util.fastMap
|
||||
import androidx.compose.ui.util.fastMapIndexedNotNull
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import eu.kanade.core.util.insertSeparatorsReversed
|
||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.YearMonth
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.datetime.yearMonth
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.LocalDate
|
||||
import java.time.YearMonth
|
||||
import kotlin.time.Clock
|
||||
|
||||
class UpcomingViewModel(
|
||||
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
|
||||
@@ -41,8 +44,14 @@ class UpcomingViewModel(
|
||||
.insertSeparatorsReversed { before, after ->
|
||||
if (after != null) mangaCount++
|
||||
|
||||
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
|
||||
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
|
||||
val beforeDate = before?.manga
|
||||
?.expectedNextUpdate
|
||||
?.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
?.date
|
||||
val afterDate = after?.manga
|
||||
?.expectedNextUpdate
|
||||
?.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
?.date
|
||||
|
||||
if (beforeDate != afterDate && afterDate != null) {
|
||||
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
|
||||
@@ -73,7 +82,10 @@ class UpcomingViewModel(
|
||||
}
|
||||
|
||||
data class State(
|
||||
val selectedYearMonth: YearMonth = YearMonth.now(),
|
||||
val selectedYearMonth: YearMonth = Clock.System.now()
|
||||
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
.date
|
||||
.yearMonth,
|
||||
val items: List<UpcomingUIModel> = listOf(),
|
||||
val events: Map<LocalDate, Int> = mapOf(),
|
||||
val headerIndexes: Map<LocalDate, Int> = mapOf(),
|
||||
|
||||
@@ -19,12 +19,15 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import io.woong.compose.grid.SimpleGridCells
|
||||
import io.woong.compose.grid.VerticalGrid
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.YearMonth
|
||||
import kotlinx.datetime.minusMonth
|
||||
import kotlinx.datetime.plusMonth
|
||||
import kotlinx.datetime.toJavaDayOfWeek
|
||||
import mihon.core.designsystem.utils.isExpandedWidthWindow
|
||||
import mihon.core.designsystem.utils.isMediumWidthWindow
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDate
|
||||
import java.time.YearMonth
|
||||
import java.time.format.TextStyle
|
||||
import java.time.temporal.WeekFields
|
||||
import java.util.Locale
|
||||
@@ -47,8 +50,8 @@ fun Calendar(
|
||||
) {
|
||||
CalenderHeader(
|
||||
yearMonth = selectedYearMonth,
|
||||
onPreviousClick = { setSelectedYearMonth(selectedYearMonth.minusMonths(1L)) },
|
||||
onNextClick = { setSelectedYearMonth(selectedYearMonth.plusMonths(1L)) },
|
||||
onPreviousClick = { setSelectedYearMonth(selectedYearMonth.minusMonth()) },
|
||||
onNextClick = { setSelectedYearMonth(selectedYearMonth.plusMonth()) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = MaterialTheme.padding.small)
|
||||
@@ -74,8 +77,8 @@ private fun CalendarGrid(
|
||||
.map { DayOfWeek.of((localeFirstDayOfWeek - 1 + it) % DAYS_OF_WEEK + 1) }
|
||||
}
|
||||
|
||||
val emptyFieldCount = weekDays.indexOf(selectedYearMonth.atDay(1).dayOfWeek)
|
||||
val daysInMonth = selectedYearMonth.lengthOfMonth()
|
||||
val emptyFieldCount = weekDays.indexOf(selectedYearMonth.firstDay.dayOfWeek.toJavaDayOfWeek())
|
||||
val daysInMonth = selectedYearMonth.numberOfDays
|
||||
|
||||
VerticalGrid(
|
||||
columns = SimpleGridCells.Fixed(DAYS_OF_WEEK),
|
||||
@@ -98,7 +101,7 @@ private fun CalendarGrid(
|
||||
}
|
||||
repeat(emptyFieldCount) { Box { } }
|
||||
repeat(daysInMonth) { dayIndex ->
|
||||
val localDate = selectedYearMonth.atDay(dayIndex + 1)
|
||||
val localDate = LocalDate(selectedYearMonth.year, selectedYearMonth.month, dayIndex + 1)
|
||||
CalendarDay(
|
||||
date = localDate,
|
||||
onDayClick = { onClickDay(localDate) },
|
||||
|
||||
@@ -19,8 +19,11 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||
import java.time.LocalDate
|
||||
import kotlin.time.Clock
|
||||
|
||||
private const val MAX_EVENTS = 3
|
||||
|
||||
@@ -31,7 +34,7 @@ fun CalendarDay(
|
||||
onDayClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val today = remember { LocalDate.now() }
|
||||
val today = remember { Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date }
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
@@ -54,10 +57,10 @@ fun CalendarDay(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = date.dayOfMonth.toString(),
|
||||
text = date.day.toString(),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 16.sp,
|
||||
color = if (date.isBefore(today)) {
|
||||
color = if (date < today) {
|
||||
MaterialTheme.colorScheme.onBackground.copy(alpha = DISABLED_ALPHA)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
|
||||
@@ -24,11 +24,16 @@ import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.YearMonth
|
||||
import kotlinx.datetime.toJavaYearMonth
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlinx.datetime.yearMonth
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.YearMonth
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlin.time.Clock
|
||||
|
||||
@Composable
|
||||
fun CalenderHeader(
|
||||
@@ -88,14 +93,14 @@ private fun AnimatedContentTransitionScope<YearMonth>.getAnimation(): ContentTra
|
||||
@ReadOnlyComposable
|
||||
private fun getTitleText(monthYear: YearMonth): String {
|
||||
val formatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.getDefault())
|
||||
return formatter.format(monthYear)
|
||||
return formatter.format(monthYear.toJavaYearMonth())
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun CalenderHeaderPreview() {
|
||||
CalenderHeader(
|
||||
yearMonth = YearMonth.now(),
|
||||
yearMonth = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.yearMonth,
|
||||
onNextClick = {},
|
||||
onPreviousClick = {},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user