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.sqldelight.async)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
// AndroidX libraries
|
// AndroidX libraries
|
||||||
implementation(libs.androidx.annotation)
|
implementation(libs.androidx.annotation)
|
||||||
implementation(libs.androidx.appCompat)
|
implementation(libs.androidx.appCompat)
|
||||||
|
|||||||
Vendored
+3
@@ -86,3 +86,6 @@
|
|||||||
# Firebase
|
# Firebase
|
||||||
-keep class com.google.firebase.installations.** { *; }
|
-keep class com.google.firebase.installations.** { *; }
|
||||||
-keep interface 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.Source
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
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.data.chapter.ChapterSanitizer
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
@@ -23,8 +26,8 @@ import tachiyomi.domain.library.service.LibraryPreferences
|
|||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class SyncChaptersWithSource(
|
class SyncChaptersWithSource(
|
||||||
private val downloadManager: DownloadManager,
|
private val downloadManager: DownloadManager,
|
||||||
@@ -57,8 +60,9 @@ class SyncChaptersWithSource(
|
|||||||
throw NoChaptersException()
|
throw NoChaptersException()
|
||||||
}
|
}
|
||||||
|
|
||||||
val now = ZonedDateTime.now()
|
val timeZone = TimeZone.currentSystemDefault()
|
||||||
val nowMillis = now.toInstant().toEpochMilli()
|
val now = Clock.System.now().toLocalDateTime(timeZone)
|
||||||
|
val nowMillis = now.toInstant(timeZone).toEpochMilliseconds()
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
val sourceChapters = rawSourceChapters
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
@@ -145,6 +149,7 @@ class SyncChaptersWithSource(
|
|||||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
updateManga.awaitUpdateFetchInterval(
|
updateManga.awaitUpdateFetchInterval(
|
||||||
manga,
|
manga,
|
||||||
|
timeZone,
|
||||||
now,
|
now,
|
||||||
fetchWindow,
|
fetchWindow,
|
||||||
)
|
)
|
||||||
@@ -217,7 +222,7 @@ class SyncChaptersWithSource(
|
|||||||
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
}
|
}
|
||||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
updateManga.awaitUpdateFetchInterval(manga, timeZone, now, fetchWindow)
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
// Set this manga as updated since chapters were changed
|
||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
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.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import java.time.ZonedDateTime
|
|
||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
@@ -22,25 +24,31 @@ class UpdateManga(
|
|||||||
|
|
||||||
suspend fun awaitUpdateFetchInterval(
|
suspend fun awaitUpdateFetchInterval(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
timeZone: TimeZone = TimeZone.currentSystemDefault(),
|
||||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
dateTime: LocalDateTime = Clock.System.now().toLocalDateTime(timeZone),
|
||||||
|
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime.date, timeZone),
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return mangaRepository.update(
|
return mangaRepository.update(
|
||||||
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
fetchInterval.toMangaUpdate(manga, dateTime, timeZone, window),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
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 {
|
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 {
|
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||||
val dateAdded = when (favorite) {
|
val dateAdded = when (favorite) {
|
||||||
true -> Instant.now().toEpochMilli()
|
true -> Clock.System.now().toEpochMilliseconds()
|
||||||
false -> 0
|
false -> 0
|
||||||
}
|
}
|
||||||
return mangaRepository.update(
|
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.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
@@ -18,7 +19,6 @@ import tachiyomi.domain.manga.model.Manga
|
|||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
class AddTracks(
|
class AddTracks(
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
@@ -62,8 +62,8 @@ class AddTracks(
|
|||||||
|
|
||||||
firstReadChapterDate?.let {
|
firstReadChapterDate?.let {
|
||||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||||
ZoneOffset.systemDefault(),
|
TimeZone.currentSystemDefault(),
|
||||||
ZoneOffset.UTC,
|
TimeZone.UTC,
|
||||||
)
|
)
|
||||||
track = track.copy(
|
track = track.copy(
|
||||||
startDate = startDate,
|
startDate = startDate,
|
||||||
|
|||||||
@@ -5,23 +5,23 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
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.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun relativeDateText(
|
fun relativeDateText(
|
||||||
dateEpochMillis: Long,
|
dateEpochMillis: Long,
|
||||||
): String {
|
): String {
|
||||||
return relativeDateText(
|
return relativeDateText(
|
||||||
localDate = LocalDate.ofInstant(
|
localDate = Instant.fromEpochMilliseconds(dateEpochMillis)
|
||||||
Instant.ofEpochMilli(dateEpochMillis),
|
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
ZoneId.systemDefault(),
|
.date
|
||||||
)
|
|
||||||
.takeIf { dateEpochMillis != 0L },
|
.takeIf { dateEpochMillis != 0L },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import eu.kanade.presentation.components.relativeDateText
|
|||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
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.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package eu.kanade.presentation.history
|
|||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.random.Random
|
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> {
|
class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.State> {
|
||||||
|
|
||||||
@@ -18,9 +21,9 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
|||||||
listOf(HistoryUiModelExamples.headerToday)
|
listOf(HistoryUiModelExamples.headerToday)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.plus(HistoryUiModelExamples.items().take(3))
|
.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.items().take(1))
|
||||||
.plus(HistoryUiModelExamples.header { it.minus(2, ChronoUnit.DAYS) })
|
.plus(HistoryUiModelExamples.header { it.minus(2.days) })
|
||||||
.plus(HistoryUiModelExamples.items().take(7))
|
.plus(HistoryUiModelExamples.items().take(7))
|
||||||
.toList(),
|
.toList(),
|
||||||
dialog = null,
|
dialog = null,
|
||||||
@@ -72,10 +75,12 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
|||||||
private object HistoryUiModelExamples {
|
private object HistoryUiModelExamples {
|
||||||
val headerToday = header()
|
val headerToday = header()
|
||||||
val headerTomorrow =
|
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 }) =
|
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 {
|
fun items() = sequence {
|
||||||
var count = 1
|
var count = 1
|
||||||
@@ -94,7 +99,7 @@ class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.
|
|||||||
mangaId = Random.nextLong(),
|
mangaId = Random.nextLong(),
|
||||||
title = "Test Title",
|
title = "Test Title",
|
||||||
chapterNumber = Random.nextDouble(),
|
chapterNumber = Random.nextDouble(),
|
||||||
readAt = Date.from(Instant.now()),
|
readAt = Date.from(Clock.System.now().toJavaInstant()),
|
||||||
readDuration = Random.nextLong(),
|
readDuration = Random.nextLong(),
|
||||||
coverData = MangaCover(
|
coverData = MangaCover(
|
||||||
mangaId = Random.nextLong(),
|
mangaId = Random.nextLong(),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreen(
|
fun MangaScreen(
|
||||||
|
|||||||
@@ -20,15 +20,17 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.daysUntil
|
||||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.Instant
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteChaptersDialog(
|
fun DeleteChaptersDialog(
|
||||||
@@ -72,8 +74,8 @@ fun SetIntervalDialog(
|
|||||||
|
|
||||||
val nextUpdateDays = remember(nextUpdate) {
|
val nextUpdateDays = remember(nextUpdate) {
|
||||||
return@remember if (nextUpdate != null) {
|
return@remember if (nextUpdate != null) {
|
||||||
val now = Instant.now()
|
val now = Clock.System.now()
|
||||||
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
now.daysUntil(nextUpdate, TimeZone.currentSystemDefault()).coerceAtLeast(0)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ import eu.kanade.presentation.components.DropdownMenu
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.daysUntil
|
||||||
import org.intellij.markdown.MarkdownElementTypes
|
import org.intellij.markdown.MarkdownElementTypes
|
||||||
import org.intellij.markdown.MarkdownTokenTypes
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
import org.intellij.markdown.ast.findChildOfType
|
import org.intellij.markdown.ast.findChildOfType
|
||||||
@@ -105,9 +107,9 @@ import tachiyomi.presentation.core.util.clickableNoIndication
|
|||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaInfoBox(
|
fun MangaInfoBox(
|
||||||
@@ -189,8 +191,8 @@ fun MangaActionRow(
|
|||||||
// TODO: show something better when using custom interval
|
// TODO: show something better when using custom interval
|
||||||
val nextUpdateDays = remember(nextUpdate) {
|
val nextUpdateDays = remember(nextUpdate) {
|
||||||
return@remember if (nextUpdate != null) {
|
return@remember if (nextUpdate != null) {
|
||||||
val now = Instant.now()
|
val now = Clock.System.now()
|
||||||
now.until(nextUpdate, ChronoUnit.DAYS).toInt().coerceAtLeast(0)
|
now.daysUntil(nextUpdate, TimeZone.currentSystemDefault()).coerceAtLeast(0)
|
||||||
} else {
|
} else {
|
||||||
null
|
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.AppThemeModePreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
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.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.LocalDate
|
import kotlin.time.Clock
|
||||||
|
|
||||||
object SettingsAppearanceScreen : SearchableSettings {
|
object SettingsAppearanceScreen : SearchableSettings {
|
||||||
|
|
||||||
@@ -99,7 +102,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
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 dateFormat by uiPreferences.dateFormat.collectAsState()
|
||||||
val formattedNow = remember(dateFormat) {
|
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.toast
|
||||||
import eu.kanade.tachiyomi.util.system.updaterEnabled
|
import eu.kanade.tachiyomi.util.system.updaterEnabled
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
@@ -59,9 +61,7 @@ import tachiyomi.presentation.core.icons.Reddit
|
|||||||
import tachiyomi.presentation.core.icons.X
|
import tachiyomi.presentation.core.icons.X
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
object AboutScreen : Screen() {
|
object AboutScreen : Screen() {
|
||||||
|
|
||||||
@@ -273,16 +273,14 @@ object AboutScreen : Screen() {
|
|||||||
|
|
||||||
internal fun getFormattedBuildTime(): String {
|
internal fun getFormattedBuildTime(): String {
|
||||||
return try {
|
return try {
|
||||||
LocalDateTime.ofInstant(
|
Instant.parse(BuildConfig.BUILD_TIME)
|
||||||
Instant.parse(BuildConfig.BUILD_TIME),
|
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
ZoneId.systemDefault(),
|
|
||||||
)
|
|
||||||
.toDateTimestampString(
|
.toDateTimestampString(
|
||||||
UiPreferences.dateFormat(
|
UiPreferences.dateFormat(
|
||||||
Injekt.get<UiPreferences>().dateFormat.get(),
|
Injekt.get<UiPreferences>().dateFormat.get(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
BuildConfig.BUILD_TIME
|
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.SharingStarted
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
class WorkerInfoScreen : Screen() {
|
class WorkerInfoScreen : Screen() {
|
||||||
|
|
||||||
@@ -165,10 +165,8 @@ class WorkerInfoScreen : Screen() {
|
|||||||
}
|
}
|
||||||
appendLine("State: ${workInfo.state}")
|
appendLine("State: ${workInfo.state}")
|
||||||
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||||
val timestamp = LocalDateTime.ofInstant(
|
val timestamp = Instant.fromEpochMilliseconds(workInfo.nextScheduleTimeMillis)
|
||||||
Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis),
|
.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
ZoneId.systemDefault(),
|
|
||||||
)
|
|
||||||
.toDateTimestampString(
|
.toDateTimestampString(
|
||||||
UiPreferences.dateFormat(
|
UiPreferences.dateFormat(
|
||||||
Injekt.get<UiPreferences>().dateFormat.get(),
|
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.presentation.track.components.TrackLogoIcon
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
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.lang.toLocalDate
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import kotlinx.datetime.toJavaLocalDate
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@@ -110,11 +112,13 @@ fun TrackInfoDialogHome(
|
|||||||
.takeIf { supportsScoring && item.track.score != 0.0 },
|
.takeIf { supportsScoring && item.track.score != 0.0 },
|
||||||
onScoreClick = { onScoreClick(item) }
|
onScoreClick = { onScoreClick(item) }
|
||||||
.takeIf { supportsScoring },
|
.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 },
|
.takeIf { supportsReadingDates && item.track.startDate != 0L },
|
||||||
onStartDateClick = { onStartDateEdit(item) } // TODO
|
onStartDateClick = { onStartDateEdit(item) } // TODO
|
||||||
.takeIf { supportsReadingDates },
|
.takeIf { supportsReadingDates },
|
||||||
endDate = dateFormat.format(item.track.finishDate.toLocalDate())
|
endDate = dateFormat.format(item.track.finishDate.toJavaLocalDate())
|
||||||
.takeIf { supportsReadingDates && item.track.finishDate != 0L },
|
.takeIf { supportsReadingDates && item.track.finishDate != 0L },
|
||||||
onEndDateClick = { onEndDateEdit(item) }
|
onEndDateClick = { onEndDateEdit(item) }
|
||||||
.takeIf { supportsReadingDates },
|
.takeIf { supportsReadingDates },
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.Instant
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.toJavaInstant
|
||||||
|
|
||||||
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||||
private val fullPageWithSecondSelected = @Composable {
|
private val fullPageWithSecondSelected = @Composable {
|
||||||
@@ -93,7 +94,7 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
|||||||
it.finished_reading_date = 0L
|
it.finished_reading_date = 0L
|
||||||
it.tracking_url = "https://example.com/tracker-example"
|
it.tracking_url = "https://example.com/tracker-example"
|
||||||
it.cover_url = "https://example.com/cover.png"
|
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.summary = lorem((0..40).random()).joinToString()
|
||||||
it.publishing_status = if (Random.nextBoolean()) "Finished" else ""
|
it.publishing_status = if (Random.nextBoolean()) "Finished" else ""
|
||||||
it.publishing_type = if (Random.nextBoolean()) "Oneshot" 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 eu.kanade.tachiyomi.ui.updates.UpdatesViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
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.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.theme.active
|
import tachiyomi.presentation.core.theme.active
|
||||||
import java.time.LocalDate
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
|
|||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
fun relativeTimeSpanString(epochMillis: Long): String {
|
fun relativeTimeSpanString(epochMillis: Long): String {
|
||||||
val now = Instant.now().toEpochMilli()
|
val now = Clock.System.now().toEpochMilliseconds()
|
||||||
return when {
|
return when {
|
||||||
epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never)
|
epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never)
|
||||||
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(
|
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import tachiyomi.core.common.storage.displayablePath
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class BackupNotifier(private val context: Context) {
|
class BackupNotifier(private val context: Context) {
|
||||||
|
|
||||||
@@ -149,10 +149,8 @@ class BackupNotifier(private val context: Context) {
|
|||||||
|
|
||||||
val timeString = context.stringResource(
|
val timeString = context.stringResource(
|
||||||
MR.strings.restore_duration,
|
MR.strings.restore_duration,
|
||||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
time.milliseconds.inWholeMinutes,
|
||||||
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
|
time.milliseconds.inWholeSeconds - (time.milliseconds.inWholeMinutes * 60),
|
||||||
TimeUnit.MILLISECONDS.toMinutes(time),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with(completeNotificationBuilder) {
|
with(completeNotificationBuilder) {
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class BackupCreator(
|
class BackupCreator(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -108,7 +108,7 @@ class BackupCreator(
|
|||||||
BackupFileValidator(context).validate(fileUri)
|
BackupFileValidator(context).validate(fileUri)
|
||||||
|
|
||||||
if (isAutoBackup) {
|
if (isAutoBackup) {
|
||||||
backupPreferences.lastAutoBackupTimestamp.set(Instant.now().toEpochMilli())
|
backupPreferences.lastAutoBackupTimestamp.set(Clock.System.now().toEpochMilliseconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileUri.toString()
|
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.BackupHistory
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
|
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import tachiyomi.data.Database
|
import tachiyomi.data.Database
|
||||||
import tachiyomi.data.MemoColumnAdapter
|
import tachiyomi.data.MemoColumnAdapter
|
||||||
import tachiyomi.data.UpdateStrategyColumnAdapter
|
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||||
@@ -23,9 +25,9 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
|||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class MangaRestorer(
|
class MangaRestorer(
|
||||||
private val database: Database = Injekt.get(),
|
private val database: Database = Injekt.get(),
|
||||||
@@ -38,13 +40,9 @@ class MangaRestorer(
|
|||||||
fetchInterval: FetchInterval = Injekt.get(),
|
fetchInterval: FetchInterval = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var now = ZonedDateTime.now()
|
private val timeZone = TimeZone.currentSystemDefault()
|
||||||
private var currentFetchWindow = fetchInterval.getWindow(now)
|
private val now = Clock.System.now().toLocalDateTime(timeZone)
|
||||||
|
private val currentFetchWindow = fetchInterval.getWindow(now.date, timeZone)
|
||||||
init {
|
|
||||||
now = ZonedDateTime.now()
|
|
||||||
currentFetchWindow = fetchInterval.getWindow(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
|
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
|
||||||
val urlsBySource = database.mangasQueries
|
val urlsBySource = database.mangasQueries
|
||||||
@@ -286,7 +284,7 @@ class MangaRestorer(
|
|||||||
restoreTracking(manga, tracks)
|
restoreTracking(manga, tracks)
|
||||||
restoreHistory(manga, history)
|
restoreHistory(manga, history)
|
||||||
restoreExcludedScanlators(manga, excludedScanlators)
|
restoreExcludedScanlators(manga, excludedScanlators)
|
||||||
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
|
updateManga.awaitUpdateFetchInterval(manga, timeZone, now, currentFetchWindow)
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import kotlinx.coroutines.coroutineScope
|
|||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||||
@@ -64,14 +66,13 @@ import tachiyomi.i18n.MR
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.Instant
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.atomics.AtomicBoolean
|
import kotlin.concurrent.atomics.AtomicBoolean
|
||||||
import kotlin.concurrent.atomics.AtomicInt
|
import kotlin.concurrent.atomics.AtomicInt
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||||
import kotlin.concurrent.atomics.incrementAndFetch
|
import kotlin.concurrent.atomics.incrementAndFetch
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
@OptIn(ExperimentalAtomicApi::class)
|
@OptIn(ExperimentalAtomicApi::class)
|
||||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
@@ -108,7 +109,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
setForegroundSafely()
|
setForegroundSafely()
|
||||||
|
|
||||||
libraryPreferences.lastUpdatedTimestamp.set(Instant.now().toEpochMilli())
|
libraryPreferences.lastUpdatedTimestamp.set(Clock.System.now().toEpochMilliseconds())
|
||||||
|
|
||||||
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
||||||
addMangaToQueue(categoryId)
|
addMangaToQueue(categoryId)
|
||||||
@@ -167,7 +168,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
val restrictions = libraryPreferences.autoUpdateMangaRestrictions.get()
|
val restrictions = libraryPreferences.autoUpdateMangaRestrictions.get()
|
||||||
val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
|
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
|
mangaToUpdate = listToUpdate
|
||||||
.filter {
|
.filter {
|
||||||
@@ -234,7 +239,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
|
||||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||||
val hasDownloads = AtomicBoolean(false)
|
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 {
|
coroutineScope {
|
||||||
mangaToUpdate.groupBy { it.manga.source }.values
|
mangaToUpdate.groupBy { it.manga.source }.values
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.io.ByteArrayInputStream
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class ImageSaver(
|
class ImageSaver(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@@ -85,7 +85,7 @@ class ImageSaver(
|
|||||||
MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
|
MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
|
MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
|
||||||
MediaStore.MediaColumns.MIME_TYPE to type.mime,
|
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) {
|
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.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
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.Json
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -25,10 +28,8 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import uy.kohesive.injekt.injectLazy
|
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.Duration.Companion.minutes
|
||||||
|
import kotlin.time.Instant
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
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 {
|
return buildJsonObject {
|
||||||
put("year", dateTime.year)
|
put("year", dateTime.year)
|
||||||
put("month", dateTime.monthValue)
|
put("month", dateTime.month.number)
|
||||||
put("day", dateTime.dayOfMonth)
|
put("day", dateTime.day)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.anilist.dto
|
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 kotlinx.serialization.Serializable
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ALFuzzyDate(
|
data class ALFuzzyDate(
|
||||||
@@ -11,10 +12,9 @@ data class ALFuzzyDate(
|
|||||||
val day: Int?,
|
val day: Int?,
|
||||||
) {
|
) {
|
||||||
fun toEpochMilli(): Long = try {
|
fun toEpochMilli(): Long = try {
|
||||||
LocalDate.of(year!!, month!!, day!!)
|
LocalDate(year!!, month!!, day!!)
|
||||||
.atStartOfDay(ZoneId.systemDefault())
|
.atStartOfDayIn(TimeZone.currentSystemDefault())
|
||||||
.toInstant()
|
.toEpochMilliseconds()
|
||||||
.toEpochMilli()
|
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
0L
|
0L
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -49,7 +49,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
|
|||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||||
|
|
||||||
class BrowseSourceViewModel(
|
class BrowseSourceViewModel(
|
||||||
@@ -226,7 +226,7 @@ class BrowseSourceViewModel(
|
|||||||
favorite = !manga.favorite,
|
favorite = !manga.favorite,
|
||||||
dateAdded = when (manga.favorite) {
|
dateAdded = when (manga.favorite) {
|
||||||
true -> 0
|
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.TrackerManager
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
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.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
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.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import mihon.core.viewmodel.StateViewModel
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
@@ -85,9 +86,8 @@ import tachiyomi.presentation.core.components.material.padding
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import java.time.LocalDate
|
import kotlin.time.Instant
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
data class TrackInfoDialogHomeScreen(
|
data class TrackInfoDialogHomeScreen(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
@@ -513,20 +513,20 @@ private data class TrackDateSelectorScreen(
|
|||||||
@Transient
|
@Transient
|
||||||
private val selectableDates = object : SelectableDates {
|
private val selectableDates = object : SelectableDates {
|
||||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
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
|
// Disallow future dates
|
||||||
if (targetDate > LocalDate.now(ZoneOffset.UTC)) return false
|
if (targetDate > Clock.System.now().toLocalDateTime(TimeZone.UTC)) return false
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
// Disallow setting start date after finish date
|
// Disallow setting start date after finish date
|
||||||
start && track.finishDate > 0 -> {
|
start && track.finishDate > 0 -> {
|
||||||
val finishDate = Instant.ofEpochMilli(track.finishDate).toLocalDate(ZoneOffset.UTC)
|
val finishDate = Instant.fromEpochMilliseconds(track.finishDate).toLocalDateTime(TimeZone.UTC)
|
||||||
targetDate <= finishDate
|
targetDate <= finishDate
|
||||||
}
|
}
|
||||||
// Disallow setting finish date before start date
|
// Disallow setting finish date before start date
|
||||||
!start && track.startDate > 0 -> {
|
!start && track.startDate > 0 -> {
|
||||||
val startDate = Instant.ofEpochMilli(track.startDate).toLocalDate(ZoneOffset.UTC)
|
val startDate = Instant.fromEpochMilliseconds(track.startDate).toLocalDateTime(TimeZone.UTC)
|
||||||
startDate <= targetDate
|
startDate <= targetDate
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -537,17 +537,17 @@ private data class TrackDateSelectorScreen(
|
|||||||
|
|
||||||
override fun isSelectableYear(year: Int): Boolean {
|
override fun isSelectableYear(year: Int): Boolean {
|
||||||
// Disallow future years
|
// 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 {
|
return when {
|
||||||
// Disallow setting start year after finish year
|
// Disallow setting start year after finish year
|
||||||
start && track.finishDate > 0 -> {
|
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
|
year <= finishDate.year
|
||||||
}
|
}
|
||||||
// Disallow setting finish year before start year
|
// Disallow setting finish year before start year
|
||||||
!start && track.startDate > 0 -> {
|
!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
|
startDate.year <= year
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -618,14 +618,14 @@ private data class TrackDateSelectorScreen(
|
|||||||
get() {
|
get() {
|
||||||
val millis = (if (start) track.startDate else track.finishDate)
|
val millis = (if (start) track.startDate else track.finishDate)
|
||||||
.takeIf { it != 0L }
|
.takeIf { it != 0L }
|
||||||
?: Instant.now().toEpochMilli()
|
?: Clock.System.now().toEpochMilliseconds()
|
||||||
return millis.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
return millis.convertEpochMillisZone(TimeZone.currentSystemDefault(), TimeZone.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// In UTC
|
// In UTC
|
||||||
fun setDate(millis: Long) {
|
fun setDate(millis: Long) {
|
||||||
// Convert to local time
|
// Convert to local time
|
||||||
val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
|
val localMillis = millis.convertEpochMillisZone(TimeZone.UTC, TimeZone.currentSystemDefault())
|
||||||
viewModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
if (start) {
|
if (start) {
|
||||||
tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
|
tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
|
||||||
|
|||||||
@@ -76,8 +76,9 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import kotlin.getValue
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter used by the activity to perform background operations.
|
* Presenter used by the activity to perform background operations.
|
||||||
@@ -582,7 +583,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun restartReadTimer() {
|
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.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.DateTimeUnit
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.minus
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import mihon.core.viewmodel.StateViewModel
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
@@ -49,7 +52,7 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
|
|||||||
import tachiyomi.domain.updates.service.UpdatesPreferences
|
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZonedDateTime
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class UpdatesViewModel(
|
class UpdatesViewModel(
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
@@ -77,7 +80,7 @@ class UpdatesViewModel(
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
// Set date limit for recent chapters
|
// 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(
|
combine(
|
||||||
// needed for SQL filters (unread, started, bookmarked, etc)
|
// 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.createFileInCacheDir
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
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.withNonCancellableContext
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.OffsetDateTime
|
import kotlin.time.Clock
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
class CrashLogUtil(
|
class CrashLogUtil(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -35,12 +37,14 @@ class CrashLogUtil(
|
|||||||
|
|
||||||
val uri = file.getUriCompat(context)
|
val uri = file.getUriCompat(context)
|
||||||
context.startActivity(uri.toShareIntent(context, "text/plain"))
|
context.startActivity(uri.toShareIntent(context, "text/plain"))
|
||||||
} catch (e: Throwable) {
|
} catch (_: Throwable) {
|
||||||
withUIContext { context.toast("Failed to get logs") }
|
withUIContext { context.toast("Failed to get logs") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDebugInfo(): String {
|
fun getDebugInfo(): String {
|
||||||
|
val now = Clock.System.now()
|
||||||
|
val tz = TimeZone.currentSystemDefault()
|
||||||
return """
|
return """
|
||||||
App ID: ${BuildConfig.APPLICATION_ID}
|
App ID: ${BuildConfig.APPLICATION_ID}
|
||||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME})
|
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 name: ${Build.DEVICE} (${Build.PRODUCT})
|
||||||
Device model: ${Build.MODEL}
|
Device model: ${Build.MODEL}
|
||||||
WebView: ${WebViewUtil.getVersion(context)}
|
WebView: ${WebViewUtil.getVersion(context)}
|
||||||
Current time: ${OffsetDateTime.now(ZoneId.systemDefault())}
|
Current time: ${now.toLocalDateTime(tz)}${tz.offsetAt(now)}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import tachiyomi.source.local.isLocal
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
|
|
||||||
fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
|
fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
|
||||||
if (isLocal()) return this
|
if (isLocal()) return this
|
||||||
return if (coverCache.deleteFromCache(this, true) > 0) {
|
return if (coverCache.deleteFromCache(this, true) > 0) {
|
||||||
copy(coverLastModified = Instant.now().toEpochMilli())
|
copy(coverLastModified = Clock.System.now().toEpochMilliseconds())
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
package eu.kanade.tachiyomi.util.lang
|
package eu.kanade.tachiyomi.util.lang
|
||||||
|
|
||||||
import android.content.Context
|
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.pluralStringResource
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.text.DateFormat
|
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.DateTimeFormatter
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
|
fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
|
||||||
val date = dateTimeFormatter.format(this)
|
val javaLocalDateTime = this.toJavaLocalDateTime()
|
||||||
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(this)
|
val date = dateTimeFormatter.format(javaLocalDateTime)
|
||||||
|
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(javaLocalDateTime)
|
||||||
return "$date $time"
|
return "$date $time"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,21 +32,21 @@ fun Date.toTimestampString(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Long.convertEpochMillisZone(
|
fun Long.convertEpochMillisZone(
|
||||||
from: ZoneId,
|
from: TimeZone,
|
||||||
to: ZoneId,
|
to: TimeZone,
|
||||||
): Long {
|
): Long {
|
||||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), from)
|
return Instant.fromEpochMilliseconds(this)
|
||||||
.atZone(to)
|
.toLocalDateTime(from)
|
||||||
.toInstant()
|
.toInstant(to)
|
||||||
.toEpochMilli()
|
.toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Long.toLocalDate(): LocalDate {
|
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 {
|
fun Long.toJavaLocalDate(): java.time.LocalDate {
|
||||||
return LocalDate.ofInstant(this, zoneId)
|
return this.toLocalDate().toJavaLocalDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LocalDate.toRelativeString(
|
fun LocalDate.toRelativeString(
|
||||||
@@ -49,23 +55,25 @@ fun LocalDate.toRelativeString(
|
|||||||
dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
|
dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
|
||||||
): String {
|
): String {
|
||||||
if (!relative) {
|
if (!relative) {
|
||||||
return dateFormat.format(this)
|
return dateFormat.format(this.toJavaLocalDate())
|
||||||
}
|
}
|
||||||
val now = LocalDate.now()
|
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||||
val difference = ChronoUnit.DAYS.between(this, now)
|
val difference = (today - this).days
|
||||||
return when {
|
return when {
|
||||||
difference < -7 -> dateFormat.format(this)
|
difference < -7 -> dateFormat.format(this.toJavaLocalDate())
|
||||||
difference < 0 -> context.pluralStringResource(
|
difference < 0 -> context.pluralStringResource(
|
||||||
MR.plurals.upcoming_relative_time,
|
MR.plurals.upcoming_relative_time,
|
||||||
difference.toInt().absoluteValue,
|
difference.absoluteValue,
|
||||||
difference.toInt().absoluteValue,
|
difference.absoluteValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
difference < 1 -> context.stringResource(MR.strings.relative_time_today)
|
difference < 1 -> context.stringResource(MR.strings.relative_time_today)
|
||||||
difference < 7 -> context.pluralStringResource(
|
difference < 7 -> context.pluralStringResource(
|
||||||
MR.plurals.relative_time,
|
MR.plurals.relative_time,
|
||||||
difference.toInt(),
|
difference,
|
||||||
difference.toInt(),
|
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.source.service.SourceManager
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class MigrateMangaUseCase(
|
class MigrateMangaUseCase(
|
||||||
private val sourcePreferences: SourcePreferences,
|
private val sourcePreferences: SourcePreferences,
|
||||||
@@ -124,7 +124,7 @@ class MigrateMangaUseCase(
|
|||||||
favorite = true,
|
favorite = true,
|
||||||
chapterFlags = current.chapterFlags,
|
chapterFlags = current.chapterFlags,
|
||||||
viewerFlags = current.viewerFlags,
|
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,
|
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.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
|
|
||||||
class UpdateMangaFromRemote(
|
class UpdateMangaFromRemote(
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
@@ -107,14 +107,14 @@ class UpdateMangaFromRemote(
|
|||||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
localManga.isLocal() -> Clock.System.now().toEpochMilliseconds()
|
||||||
localManga.hasCustomCover(coverCache) -> {
|
localManga.hasCustomCover(coverCache) -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
Instant.now().toEpochMilli()
|
Clock.System.now().toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package mihon.feature.library
|
package mihon.feature.library
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
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.AndNode
|
||||||
import mihon.domain.library.model.search.ComparisonField
|
import mihon.domain.library.model.search.ComparisonField
|
||||||
import mihon.domain.library.model.search.ComparisonQueryNode
|
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.OrNode
|
||||||
import mihon.domain.library.model.search.QueryNode
|
import mihon.domain.library.model.search.QueryNode
|
||||||
import tachiyomi.source.local.LocalSource
|
import tachiyomi.source.local.LocalSource
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.ZoneId
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
fun QueryNode.matches(item: LibraryItem): Boolean {
|
fun QueryNode.matches(item: LibraryItem): Boolean {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
@@ -109,7 +110,7 @@ private fun ComparisonQueryNode.matches(item: LibraryItem): Boolean {
|
|||||||
|
|
||||||
fun compareDates(timestamp: Long, value: String): Boolean? {
|
fun compareDates(timestamp: Long, value: String): Boolean? {
|
||||||
val inputDate = runCatching { LocalDate.parse(value) }.getOrNull() ?: return null
|
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)
|
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.components.relativeDateText
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.YearMonth
|
||||||
import mihon.feature.upcoming.components.UpcomingItem
|
import mihon.feature.upcoming.components.UpcomingItem
|
||||||
import mihon.feature.upcoming.components.calendar.Calendar
|
import mihon.feature.upcoming.components.calendar.Calendar
|
||||||
import tachiyomi.core.common.Constants
|
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.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.YearMonth
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpcomingScreenContent(
|
fun UpcomingScreenContent(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package mihon.feature.upcoming
|
package mihon.feature.upcoming
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
sealed interface UpcomingUIModel {
|
sealed interface UpcomingUIModel {
|
||||||
data class Header(val date: LocalDate, val mangaCount: Int) : 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.compose.ui.util.fastMapIndexedNotNull
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.kanade.core.util.insertSeparatorsReversed
|
import eu.kanade.core.util.insertSeparatorsReversed
|
||||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
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.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.LocalDate
|
import kotlin.time.Clock
|
||||||
import java.time.YearMonth
|
|
||||||
|
|
||||||
class UpcomingViewModel(
|
class UpcomingViewModel(
|
||||||
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
|
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
|
||||||
@@ -41,8 +44,14 @@ class UpcomingViewModel(
|
|||||||
.insertSeparatorsReversed { before, after ->
|
.insertSeparatorsReversed { before, after ->
|
||||||
if (after != null) mangaCount++
|
if (after != null) mangaCount++
|
||||||
|
|
||||||
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
|
val beforeDate = before?.manga
|
||||||
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
|
?.expectedNextUpdate
|
||||||
|
?.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
|
?.date
|
||||||
|
val afterDate = after?.manga
|
||||||
|
?.expectedNextUpdate
|
||||||
|
?.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
|
?.date
|
||||||
|
|
||||||
if (beforeDate != afterDate && afterDate != null) {
|
if (beforeDate != afterDate && afterDate != null) {
|
||||||
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
|
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
|
||||||
@@ -73,7 +82,10 @@ class UpcomingViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class State(
|
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 items: List<UpcomingUIModel> = listOf(),
|
||||||
val events: Map<LocalDate, Int> = mapOf(),
|
val events: Map<LocalDate, Int> = mapOf(),
|
||||||
val headerIndexes: 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 androidx.compose.ui.util.fastForEach
|
||||||
import io.woong.compose.grid.SimpleGridCells
|
import io.woong.compose.grid.SimpleGridCells
|
||||||
import io.woong.compose.grid.VerticalGrid
|
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.isExpandedWidthWindow
|
||||||
import mihon.core.designsystem.utils.isMediumWidthWindow
|
import mihon.core.designsystem.utils.isMediumWidthWindow
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.YearMonth
|
|
||||||
import java.time.format.TextStyle
|
import java.time.format.TextStyle
|
||||||
import java.time.temporal.WeekFields
|
import java.time.temporal.WeekFields
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -47,8 +50,8 @@ fun Calendar(
|
|||||||
) {
|
) {
|
||||||
CalenderHeader(
|
CalenderHeader(
|
||||||
yearMonth = selectedYearMonth,
|
yearMonth = selectedYearMonth,
|
||||||
onPreviousClick = { setSelectedYearMonth(selectedYearMonth.minusMonths(1L)) },
|
onPreviousClick = { setSelectedYearMonth(selectedYearMonth.minusMonth()) },
|
||||||
onNextClick = { setSelectedYearMonth(selectedYearMonth.plusMonths(1L)) },
|
onNextClick = { setSelectedYearMonth(selectedYearMonth.plusMonth()) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = MaterialTheme.padding.small)
|
.padding(vertical = MaterialTheme.padding.small)
|
||||||
@@ -74,8 +77,8 @@ private fun CalendarGrid(
|
|||||||
.map { DayOfWeek.of((localeFirstDayOfWeek - 1 + it) % DAYS_OF_WEEK + 1) }
|
.map { DayOfWeek.of((localeFirstDayOfWeek - 1 + it) % DAYS_OF_WEEK + 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val emptyFieldCount = weekDays.indexOf(selectedYearMonth.atDay(1).dayOfWeek)
|
val emptyFieldCount = weekDays.indexOf(selectedYearMonth.firstDay.dayOfWeek.toJavaDayOfWeek())
|
||||||
val daysInMonth = selectedYearMonth.lengthOfMonth()
|
val daysInMonth = selectedYearMonth.numberOfDays
|
||||||
|
|
||||||
VerticalGrid(
|
VerticalGrid(
|
||||||
columns = SimpleGridCells.Fixed(DAYS_OF_WEEK),
|
columns = SimpleGridCells.Fixed(DAYS_OF_WEEK),
|
||||||
@@ -98,7 +101,7 @@ private fun CalendarGrid(
|
|||||||
}
|
}
|
||||||
repeat(emptyFieldCount) { Box { } }
|
repeat(emptyFieldCount) { Box { } }
|
||||||
repeat(daysInMonth) { dayIndex ->
|
repeat(daysInMonth) { dayIndex ->
|
||||||
val localDate = selectedYearMonth.atDay(dayIndex + 1)
|
val localDate = LocalDate(selectedYearMonth.year, selectedYearMonth.month, dayIndex + 1)
|
||||||
CalendarDay(
|
CalendarDay(
|
||||||
date = localDate,
|
date = localDate,
|
||||||
onDayClick = { onClickDay(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.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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 tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||||
import java.time.LocalDate
|
import kotlin.time.Clock
|
||||||
|
|
||||||
private const val MAX_EVENTS = 3
|
private const val MAX_EVENTS = 3
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ fun CalendarDay(
|
|||||||
onDayClick: () -> Unit,
|
onDayClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val today = remember { LocalDate.now() }
|
val today = remember { Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -54,10 +57,10 @@ fun CalendarDay(
|
|||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = date.dayOfMonth.toString(),
|
text = date.day.toString(),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = if (date.isBefore(today)) {
|
color = if (date < today) {
|
||||||
MaterialTheme.colorScheme.onBackground.copy(alpha = DISABLED_ALPHA)
|
MaterialTheme.colorScheme.onBackground.copy(alpha = DISABLED_ALPHA)
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onBackground
|
MaterialTheme.colorScheme.onBackground
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.YearMonth
|
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CalenderHeader(
|
fun CalenderHeader(
|
||||||
@@ -88,14 +93,14 @@ private fun AnimatedContentTransitionScope<YearMonth>.getAnimation(): ContentTra
|
|||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
private fun getTitleText(monthYear: YearMonth): String {
|
private fun getTitleText(monthYear: YearMonth): String {
|
||||||
val formatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.getDefault())
|
val formatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.getDefault())
|
||||||
return formatter.format(monthYear)
|
return formatter.format(monthYear.toJavaYearMonth())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun CalenderHeaderPreview() {
|
private fun CalenderHeaderPreview() {
|
||||||
CalenderHeader(
|
CalenderHeader(
|
||||||
yearMonth = YearMonth.now(),
|
yearMonth = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date.yearMonth,
|
||||||
onNextClick = {},
|
onNextClick = {},
|
||||||
onPreviousClick = {},
|
onPreviousClick = {},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import okhttp3.Cache
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class NetworkHelper(
|
class NetworkHelper(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -20,9 +21,9 @@ class NetworkHelper(
|
|||||||
private val clientBuilder: OkHttpClient.Builder = run {
|
private val clientBuilder: OkHttpClient.Builder = run {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
.cookieJar(cookieJar)
|
.cookieJar(cookieJar)
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30.seconds)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30.seconds)
|
||||||
.callTimeout(2, TimeUnit.MINUTES)
|
.callTimeout(2.minutes)
|
||||||
.cache(
|
.cache(
|
||||||
Cache(
|
Cache(
|
||||||
directory = File(context.cacheDir, "network_cache"),
|
directory = File(context.cacheDir, "network_cache"),
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import okhttp3.HttpUrl
|
|||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import java.util.concurrent.TimeUnit.MINUTES
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
|
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10.minutes).build()
|
||||||
private val DEFAULT_HEADERS = Headers.Builder().build()
|
private val DEFAULT_HEADERS = Headers.Builder().build()
|
||||||
private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
|
private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
|
||||||
|
|
||||||
|
|||||||
@@ -38,5 +38,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.injekt)
|
implementation(libs.injekt)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
api(libs.bundles.sqldelight)
|
api(libs.bundles.sqldelight)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import app.cash.sqldelight.async.coroutines.awaitAsList
|
|||||||
import app.cash.sqldelight.async.coroutines.awaitAsOne
|
import app.cash.sqldelight.async.coroutines.awaitAsOne
|
||||||
import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
|
import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.atStartOfDayIn
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.data.Database
|
import tachiyomi.data.Database
|
||||||
@@ -18,8 +21,7 @@ import tachiyomi.domain.manga.model.Manga
|
|||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import java.time.LocalDate
|
import kotlin.time.Clock
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
class MangaRepositoryImpl(
|
class MangaRepositoryImpl(
|
||||||
private val database: Database,
|
private val database: Database,
|
||||||
@@ -86,7 +88,9 @@ class MangaRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getUpcomingManga(statuses: Set<Long>): Flow<List<Manga>> {
|
override suspend fun getUpcomingManga(statuses: Set<Long>): Flow<List<Manga>> {
|
||||||
val epochMillis = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000
|
val timeZone = TimeZone.currentSystemDefault()
|
||||||
|
val epochMillis =
|
||||||
|
Clock.System.now().toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone).toEpochMilliseconds()
|
||||||
return database.mangasQueries
|
return database.mangasQueries
|
||||||
.getUpcomingManga(epochMillis, statuses, MangaMapper::mapManga)
|
.getUpcomingManga(epochMillis, statuses, MangaMapper::mapManga)
|
||||||
.subscribeToList()
|
.subscribeToList()
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ dependencies {
|
|||||||
implementation(libs.bundles.kotlinx.coroutines)
|
implementation(libs.bundles.kotlinx.coroutines)
|
||||||
implementation(libs.bundles.serialization)
|
implementation(libs.bundles.serialization)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
implementation(libs.unifile)
|
implementation(libs.unifile)
|
||||||
|
|
||||||
api(libs.sqldelight.androidxPaging)
|
api(libs.sqldelight.androidxPaging)
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package tachiyomi.domain.manga.interactor
|
package tachiyomi.domain.manga.interactor
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.atStartOfDayIn
|
||||||
|
import kotlinx.datetime.daysUntil
|
||||||
|
import kotlinx.datetime.toInstant
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import java.time.Instant
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
class FetchInterval(
|
class FetchInterval(
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
@@ -16,40 +22,42 @@ class FetchInterval(
|
|||||||
|
|
||||||
suspend fun toMangaUpdate(
|
suspend fun toMangaUpdate(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
dateTime: ZonedDateTime,
|
dateTime: LocalDateTime,
|
||||||
|
timeZone: TimeZone,
|
||||||
window: Pair<Long, Long>,
|
window: Pair<Long, Long>,
|
||||||
): MangaUpdate {
|
): MangaUpdate {
|
||||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||||
chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true),
|
chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true),
|
||||||
zone = dateTime.zone,
|
zone = timeZone,
|
||||||
)
|
)
|
||||||
val currentWindow = if (window.first == 0L && window.second == 0L) {
|
val currentWindow = if (window.first == 0L && window.second == 0L) {
|
||||||
getWindow(ZonedDateTime.now())
|
getWindow(Clock.System.now().toLocalDateTime(timeZone).date, timeZone)
|
||||||
} else {
|
} else {
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
|
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, timeZone, currentWindow)
|
||||||
|
|
||||||
return MangaUpdate(id = manga.id, nextUpdate = nextUpdate, fetchInterval = interval)
|
return MangaUpdate(id = manga.id, nextUpdate = nextUpdate, fetchInterval = interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
|
fun getWindow(localDateTime: LocalDate, timeZone: TimeZone): Pair<Long, Long> {
|
||||||
val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone)
|
val today = localDateTime.atStartOfDayIn(timeZone)
|
||||||
val lowerBound = today.minusDays(GRACE_PERIOD)
|
val lowerBound = today - GRACE_PERIOD.days
|
||||||
val upperBound = today.plusDays(GRACE_PERIOD)
|
val upperBound = today + GRACE_PERIOD.days
|
||||||
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
|
return Pair(lowerBound.toEpochMilliseconds(), upperBound.toEpochMilliseconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun calculateInterval(chapters: List<Chapter>, zone: ZoneId): Int {
|
internal fun calculateInterval(chapters: List<Chapter>, zone: TimeZone): Int {
|
||||||
val chapterWindow = if (chapters.size <= 8) 3 else 10
|
val chapterWindow = if (chapters.size <= 8) 3 else 10
|
||||||
|
|
||||||
val uploadDates = chapters.asSequence()
|
val uploadDates = chapters.asSequence()
|
||||||
.filter { it.dateUpload > 0L }
|
.filter { it.dateUpload > 0L }
|
||||||
.sortedByDescending { it.dateUpload }
|
.sortedByDescending { it.dateUpload }
|
||||||
.map {
|
.map {
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone)
|
Instant.fromEpochMilliseconds(it.dateUpload)
|
||||||
.toLocalDate()
|
.toLocalDateTime(zone)
|
||||||
.atStartOfDay()
|
.date
|
||||||
|
.atStartOfDayIn(zone)
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
.take(chapterWindow)
|
.take(chapterWindow)
|
||||||
@@ -58,9 +66,10 @@ class FetchInterval(
|
|||||||
val fetchDates = chapters.asSequence()
|
val fetchDates = chapters.asSequence()
|
||||||
.sortedByDescending { it.dateFetch }
|
.sortedByDescending { it.dateFetch }
|
||||||
.map {
|
.map {
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone)
|
Instant.fromEpochMilliseconds(it.dateFetch)
|
||||||
.toLocalDate()
|
.toLocalDateTime(zone)
|
||||||
.atStartOfDay()
|
.date
|
||||||
|
.atStartOfDayIn(zone)
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
.take(chapterWindow)
|
.take(chapterWindow)
|
||||||
@@ -69,13 +78,13 @@ class FetchInterval(
|
|||||||
val interval = when {
|
val interval = when {
|
||||||
// Enough upload date from source
|
// Enough upload date from source
|
||||||
uploadDates.size >= 3 -> {
|
uploadDates.size >= 3 -> {
|
||||||
val ranges = uploadDates.windowed(2).map { x -> x[1].until(x[0], ChronoUnit.DAYS) }.sorted()
|
val ranges = uploadDates.windowed(2).map { x -> x[1].daysUntil(x[0], zone) }.sorted()
|
||||||
ranges[(ranges.size - 1) / 2].toInt()
|
ranges[(ranges.size - 1) / 2]
|
||||||
}
|
}
|
||||||
// Enough fetch date from client
|
// Enough fetch date from client
|
||||||
fetchDates.size >= 3 -> {
|
fetchDates.size >= 3 -> {
|
||||||
val ranges = fetchDates.windowed(2).map { x -> x[1].until(x[0], ChronoUnit.DAYS) }.sorted()
|
val ranges = fetchDates.windowed(2).map { x -> x[1].daysUntil(x[0], zone) }.sorted()
|
||||||
ranges[(ranges.size - 1) / 2].toInt()
|
ranges[(ranges.size - 1) / 2]
|
||||||
}
|
}
|
||||||
// Default to 7 days
|
// Default to 7 days
|
||||||
else -> 7
|
else -> 7
|
||||||
@@ -87,34 +96,34 @@ class FetchInterval(
|
|||||||
private fun calculateNextUpdate(
|
private fun calculateNextUpdate(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
interval: Int,
|
interval: Int,
|
||||||
dateTime: ZonedDateTime,
|
dateTime: LocalDateTime,
|
||||||
|
timeZone: TimeZone,
|
||||||
window: Pair<Long, Long>,
|
window: Pair<Long, Long>,
|
||||||
): Long {
|
): Long {
|
||||||
if (manga.nextUpdate in window.first.rangeTo(window.second + 1)) {
|
if (manga.nextUpdate in window.first.rangeTo(window.second + 1)) {
|
||||||
return manga.nextUpdate
|
return manga.nextUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
val latestDate = ZonedDateTime.ofInstant(
|
val instant = if (manga.lastUpdate > 0) Instant.fromEpochMilliseconds(manga.lastUpdate) else Clock.System.now()
|
||||||
if (manga.lastUpdate > 0) Instant.ofEpochMilli(manga.lastUpdate) else Instant.now(),
|
val latestDate = instant.toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone)
|
||||||
dateTime.zone,
|
|
||||||
)
|
val daysSinceLatest = (dateTime.toInstant(timeZone) - latestDate).inWholeDays
|
||||||
.toLocalDate()
|
val cycle = daysSinceLatest.floorDiv(
|
||||||
.atStartOfDay()
|
|
||||||
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
|
|
||||||
val cycle = timeSinceLatest.floorDiv(
|
|
||||||
interval.absoluteValue.takeIf { interval < 0 }
|
interval.absoluteValue.takeIf { interval < 0 }
|
||||||
?: increaseInterval(interval, timeSinceLatest, increaseWhenOver = 10),
|
?: increaseInterval(interval, daysSinceLatest, increaseWhenOver = 10),
|
||||||
)
|
)
|
||||||
return latestDate.plusDays((cycle + 1) * interval.absoluteValue.toLong()).toEpochSecond(dateTime.offset) * 1000
|
|
||||||
|
val offsetDays = ((cycle + 1) * interval.absoluteValue.toLong()).days
|
||||||
|
return latestDate.plus(offsetDays).toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun increaseInterval(delta: Int, timeSinceLatest: Int, increaseWhenOver: Int): Int {
|
private fun increaseInterval(delta: Int, daysSinceLatest: Long, increaseWhenOver: Int): Int {
|
||||||
if (delta >= MAX_INTERVAL) return MAX_INTERVAL
|
if (delta >= MAX_INTERVAL) return MAX_INTERVAL
|
||||||
|
|
||||||
// double delta again if missed more than 9 check in new delta
|
// double delta again if missed more than 9 check in new delta
|
||||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
val cycle = daysSinceLatest.floorDiv(delta) + 1
|
||||||
return if (cycle > increaseWhenOver) {
|
return if (cycle > increaseWhenOver) {
|
||||||
increaseInterval(delta * 2, timeSinceLatest, increaseWhenOver)
|
increaseInterval(delta * 2, daysSinceLatest, increaseWhenOver)
|
||||||
} else {
|
} else {
|
||||||
delta
|
delta
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import kotlinx.serialization.json.JsonObject
|
|||||||
import mihon.core.common.extensions.EMPTY
|
import mihon.core.common.extensions.EMPTY
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import java.io.ObjectStreamException
|
import java.io.ObjectStreamException
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
import java.io.Serializable as JavaSerializable
|
import java.io.Serializable as JavaSerializable
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
@@ -47,7 +47,7 @@ data class Manga(
|
|||||||
val expectedNextUpdate: Instant?
|
val expectedNextUpdate: Instant?
|
||||||
get() = nextUpdate
|
get() = nextUpdate
|
||||||
.takeIf { status != SManga.COMPLETED.toLong() }
|
.takeIf { status != SManga.COMPLETED.toLong() }
|
||||||
?.let { Instant.ofEpochMilli(it) }
|
?.let { Instant.fromEpochMilliseconds(it) }
|
||||||
|
|
||||||
val sorting: Long
|
val sorting: Long
|
||||||
get() = chapterFlags and CHAPTER_SORTING_MASK
|
get() = chapterFlags and CHAPTER_SORTING_MASK
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package tachiyomi.domain.updates.interactor
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
import java.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
class GetUpdates(
|
class GetUpdates(
|
||||||
private val repository: UpdatesRepository,
|
private val repository: UpdatesRepository,
|
||||||
@@ -21,7 +21,7 @@ class GetUpdates(
|
|||||||
hideExcludedScanlators: Boolean,
|
hideExcludedScanlators: Boolean,
|
||||||
): Flow<List<UpdatesWithRelations>> {
|
): Flow<List<UpdatesWithRelations>> {
|
||||||
return repository.subscribeAll(
|
return repository.subscribeAll(
|
||||||
instant.toEpochMilli(),
|
instant.toEpochMilliseconds(),
|
||||||
limit = 500,
|
limit = 500,
|
||||||
unread = unread,
|
unread = unread,
|
||||||
started = started,
|
started = started,
|
||||||
|
|||||||
@@ -2,27 +2,27 @@ package tachiyomi.domain.manga.interactor
|
|||||||
|
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toInstant
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.parallel.Execution
|
import org.junit.jupiter.api.parallel.Execution
|
||||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
import kotlin.time.Duration.Companion.hours
|
import kotlin.time.Duration.Companion.hours
|
||||||
import kotlin.time.DurationUnit
|
import kotlin.time.DurationUnit
|
||||||
import kotlin.time.toDuration
|
import kotlin.time.toDuration
|
||||||
import kotlin.time.toJavaDuration
|
|
||||||
|
|
||||||
@Execution(ExecutionMode.CONCURRENT)
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
class FetchIntervalTest {
|
class FetchIntervalTest {
|
||||||
|
|
||||||
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
|
private val testTime = LocalDateTime.parse("2020-01-01T00:00:00")
|
||||||
private val testZoneId = ZoneOffset.UTC
|
private val testTimeZone = TimeZone.UTC
|
||||||
private var chapter = Chapter.create().copy(
|
private var chapter = Chapter.create().copy(
|
||||||
dateFetch = testTime.toEpochSecond() * 1000,
|
dateFetch = testTime.toInstant(testTimeZone).toEpochMilliseconds(),
|
||||||
dateUpload = testTime.toEpochSecond() * 1000,
|
dateUpload = testTime.toInstant(testTimeZone).toEpochMilliseconds(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private val fetchInterval = FetchInterval(mockk())
|
private val fetchInterval = FetchInterval(mockk())
|
||||||
@@ -32,12 +32,12 @@ class FetchIntervalTest {
|
|||||||
val chaptersWithUploadDate = (1..50).map {
|
val chaptersWithUploadDate = (1..50).map {
|
||||||
chapterWithTime(chapter, 1.days)
|
chapterWithTime(chapter, 1.days)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 7
|
fetchInterval.calculateInterval(chaptersWithUploadDate, testTimeZone) shouldBe 7
|
||||||
|
|
||||||
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||||
it.copy(dateUpload = 0L)
|
it.copy(dateUpload = 0L)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 7
|
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testTimeZone) shouldBe 7
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -51,7 +51,7 @@ class FetchIntervalTest {
|
|||||||
|
|
||||||
val chapters = oldChapters + newChapters
|
val chapters = oldChapters + newChapters
|
||||||
|
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -66,7 +66,7 @@ class FetchIntervalTest {
|
|||||||
|
|
||||||
val chapters = oldChapters + newChapters
|
val chapters = oldChapters + newChapters
|
||||||
|
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 7
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -74,7 +74,7 @@ class FetchIntervalTest {
|
|||||||
val chapters = (1..10).map {
|
val chapters = (1..10).map {
|
||||||
chapterWithTime(chapter, 10.hours)
|
chapterWithTime(chapter, 10.hours)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 7
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -84,7 +84,7 @@ class FetchIntervalTest {
|
|||||||
} + (1..5).map {
|
} + (1..5).map {
|
||||||
chapterWithTime(chapter, 2.days)
|
chapterWithTime(chapter, 2.days)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 7
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -92,7 +92,7 @@ class FetchIntervalTest {
|
|||||||
val chapters = (1..20).map {
|
val chapters = (1..20).map {
|
||||||
chapterWithTime(chapter, it.days)
|
chapterWithTime(chapter, it.days)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -100,7 +100,7 @@ class FetchIntervalTest {
|
|||||||
val chapters = (1..20).map {
|
val chapters = (1..20).map {
|
||||||
chapterWithTime(chapter, (15 * it).hours)
|
chapterWithTime(chapter, (15 * it).hours)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -108,7 +108,7 @@ class FetchIntervalTest {
|
|||||||
val chapters = (1..20).map {
|
val chapters = (1..20).map {
|
||||||
chapterWithTime(chapter, (2 * it).days)
|
chapterWithTime(chapter, (2 * it).days)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 2
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,12 +116,12 @@ class FetchIntervalTest {
|
|||||||
val chaptersWithUploadDate = (1..5).map {
|
val chaptersWithUploadDate = (1..5).map {
|
||||||
chapterWithTime(chapter, (25 * it).hours)
|
chapterWithTime(chapter, (25 * it).hours)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 1
|
fetchInterval.calculateInterval(chaptersWithUploadDate, testTimeZone) shouldBe 1
|
||||||
|
|
||||||
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||||
it.copy(dateUpload = 0L)
|
it.copy(dateUpload = 0L)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 1
|
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testTimeZone) shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -129,11 +129,11 @@ class FetchIntervalTest {
|
|||||||
val chapters = (1..20).map {
|
val chapters = (1..20).map {
|
||||||
chapterWithTime(chapter, (43 * it).hours)
|
chapterWithTime(chapter, (43 * it).hours)
|
||||||
}
|
}
|
||||||
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 2
|
fetchInterval.calculateInterval(chapters, testTimeZone) shouldBe 2
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
||||||
val newTime = testTime.plus(duration.toJavaDuration()).toEpochSecond() * 1000
|
val newTime = testTime.toInstant(testTimeZone).plus(duration).toEpochMilliseconds()
|
||||||
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package mihon.gradle
|
package mihon.gradle
|
||||||
|
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import java.time.LocalDateTime
|
import kotlin.time.Duration.Companion.nanoseconds
|
||||||
import java.time.ZoneOffset
|
import kotlin.time.Instant
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
// Git is needed in your system PATH for these commands to work.
|
// Git is needed in your system PATH for these commands to work.
|
||||||
// If it's not installed, you can return a random value as a workaround
|
// If it's not installed, you can return a random value as a workaround
|
||||||
@@ -18,19 +17,18 @@ fun Project.getLatestCommitSha(): String {
|
|||||||
// return "1"
|
// return "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val BUILD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useLatestCommitTime If `true`, the build time is based on the timestamp of the last Git commit;
|
* @param useLatestCommitTime If `true`, the build time is based on the timestamp of the last Git commit;
|
||||||
* otherwise, the current time is used. Both are in UTC.
|
* otherwise, the current time is used. Both are in UTC.
|
||||||
* @return A formatted string representing the build time. The format used is defined by [BUILD_TIME_FORMATTER].
|
* @return An ISO 8601 formatted string representing the build time.
|
||||||
*/
|
*/
|
||||||
fun Project.getBuildTime(useLatestCommitTime: Boolean): String {
|
fun Project.getBuildTime(useLatestCommitTime: Boolean): String {
|
||||||
return if (useLatestCommitTime) {
|
return if (useLatestCommitTime) {
|
||||||
val epoch = exec("git log -1 --format=%ct").toLong()
|
val epoch = exec("git log -1 --format=%ct").toLong()
|
||||||
Instant.ofEpochSecond(epoch).atOffset(ZoneOffset.UTC).format(BUILD_TIME_FORMATTER)
|
Instant.fromEpochSeconds(epoch).toString()
|
||||||
} else {
|
} else {
|
||||||
LocalDateTime.now(ZoneOffset.UTC).format(BUILD_TIME_FORMATTER)
|
val now = Clock.System.now()
|
||||||
|
(now - now.nanosecondsOfSecond.nanoseconds).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ junit = "6.1.2"
|
|||||||
kotest-assertions = "6.2.3"
|
kotest-assertions = "6.2.3"
|
||||||
kotlin-gradle = "2.4.10"
|
kotlin-gradle = "2.4.10"
|
||||||
kotlinx-coroutines = "1.11.0"
|
kotlinx-coroutines = "1.11.0"
|
||||||
|
kotlinx-datetime = "0.8.0"
|
||||||
kotlinx-serialization = "1.11.0"
|
kotlinx-serialization = "1.11.0"
|
||||||
ktlint = "1.8.0"
|
ktlint = "1.8.0"
|
||||||
leakCanary = "2.14"
|
leakCanary = "2.14"
|
||||||
@@ -142,6 +143,7 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
|
|||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||||
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines" }
|
||||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||||
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||||
kotlinx-serialization-jsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }
|
kotlinx-serialization-jsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }
|
||||||
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
|
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.glance.appWidget)
|
implementation(libs.androidx.glance.appWidget)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
implementation(libs.coil.core)
|
implementation(libs.coil.core)
|
||||||
|
|
||||||
api(libs.injekt)
|
api(libs.injekt)
|
||||||
|
|||||||
+7
-4
@@ -34,6 +34,9 @@ import coil3.transform.RoundedCornersTransformation
|
|||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.datetime.DateTimeUnit
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.minus
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
@@ -46,8 +49,8 @@ import tachiyomi.presentation.widget.util.appWidgetBackgroundRadius
|
|||||||
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
import tachiyomi.presentation.widget.util.calculateRowAndColumnCount
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import kotlin.time.Clock
|
||||||
import java.time.ZonedDateTime
|
import kotlin.time.Instant
|
||||||
|
|
||||||
abstract class BaseUpdatesGridGlanceWidget(
|
abstract class BaseUpdatesGridGlanceWidget(
|
||||||
private val context: Context = Injekt.get<Application>(),
|
private val context: Context = Injekt.get<Application>(),
|
||||||
@@ -90,7 +93,7 @@ abstract class BaseUpdatesGridGlanceWidget(
|
|||||||
|
|
||||||
val flow = remember {
|
val flow = remember {
|
||||||
getUpdates
|
getUpdates
|
||||||
.subscribe(false, DateLimit.toEpochMilli())
|
.subscribe(false, DateLimit.toEpochMilliseconds())
|
||||||
.map { rawData ->
|
.map { rawData ->
|
||||||
rawData.prepareData(rowCount, columnCount)
|
rawData.prepareData(rowCount, columnCount)
|
||||||
}
|
}
|
||||||
@@ -153,6 +156,6 @@ abstract class BaseUpdatesGridGlanceWidget(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DateLimit: Instant
|
val DateLimit: Instant
|
||||||
get() = ZonedDateTime.now().minusMonths(3).toInstant()
|
get() = Clock.System.now().minus(3, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class WidgetManager(
|
|||||||
|
|
||||||
fun Context.init(scope: LifecycleCoroutineScope) {
|
fun Context.init(scope: LifecycleCoroutineScope) {
|
||||||
combine(
|
combine(
|
||||||
getUpdates.subscribe(read = false, after = BaseUpdatesGridGlanceWidget.DateLimit.toEpochMilli()),
|
getUpdates.subscribe(read = false, after = BaseUpdatesGridGlanceWidget.DateLimit.toEpochMilliseconds()),
|
||||||
securityPreferences.useAuthenticator.changes(),
|
securityPreferences.useAuthenticator.changes(),
|
||||||
transform = { a, b -> a to b },
|
transform = { a, b -> a to b },
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user