diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 44a78d962..8d49fcbff 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,7 +5,7 @@ I acknowledge that: - I have updated: - To the latest version of the app (stable is v0.12.3.10) - All extensions -- I have gone through the FAQ (https://aniyomi.org/help/faq/) and troubleshooting guide (https://aniyomi.org/help/guides/troubleshooting/) +- I have gone through the FAQ (https://aniyomi.org/docs/faq/general) and troubleshooting guide (https://aniyomi.org/docs/guides/troubleshooting/) - If this is an issue with an anime extension, that I should be opening an issue in https://github.com/aniyomiorg/aniyomi-extensions - I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue - I will fill out the title and the information in this template diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b8a476ef9..85af6b80b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,7 +4,7 @@ contact_links: url: https://github.com/aniyomiorg/aniyomi-extensions/issues/new/choose about: Issues and requests for extensions and sources should be opened in the aniyomi-extensions repository instead - name: 📦 Aniyomi extensions - url: https://aniyomi.org/extensions + url: https://aniyomi.org/extensions/ about: Anime extensions and sources - name: 🧑‍💻 Aniyomi help discord url: https://discord.gg/F32UjdJZrR diff --git a/.github/ISSUE_TEMPLATE/report_issue.yml b/.github/ISSUE_TEMPLATE/report_issue.yml index 461dcc320..bc82dc47d 100644 --- a/.github/ISSUE_TEMPLATE/report_issue.yml +++ b/.github/ISSUE_TEMPLATE/report_issue.yml @@ -95,7 +95,7 @@ body: required: true - label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/aniyomiorg/aniyomi-extensions/issues/new/choose). required: true - - label: I have gone through the [FAQ](https://aniyomi.org/help/faq/) and [troubleshooting guide](https://aniyomi.org/help/guides/troubleshooting/). + - label: I have gone through the [FAQ](https://aniyomi.org/docs/faq/general) and [troubleshooting guide](https://aniyomi.org/docs/guides/troubleshooting/). required: true - label: I have updated the app to version **[0.12.3.10](https://github.com/aniyomiorg/aniyomi/releases/latest)**. required: true diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 128eb0095..e39a25da0 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -120,3 +120,14 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + update-website: + needs: [ build ] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') && github.repository == 'aniyomiorg/aniyomi' + steps: + - name: Trigger Netlify build hook + run: curl -s -X POST -d {} "https://api.netlify.com/build_hooks/${TOKEN}" + env: + TOKEN: ${{ secrets.NETLIFY_HOOK_RELEASE }} + diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml index 0961cdd8f..fa763d3aa 100644 --- a/.github/workflows/issue_moderator.yml +++ b/.github/workflows/issue_moderator.yml @@ -33,7 +33,7 @@ jobs: "regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?Issues -1. **Before reporting a new issue, take a look at the already opened [issues](https://github.com/aniyomiorg/aniyomi/issues).** +1. **Before reporting a new issue, take a look at the already opened [issues](https://aniyomi.org/changelogs/).** 2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) diff --git a/app/src/main/java/aniyomi/util/DataSaver.kt b/app/src/main/java/aniyomi/util/DataSaver.kt index 5c02e261a..a328a7c6e 100644 --- a/app/src/main/java/aniyomi/util/DataSaver.kt +++ b/app/src/main/java/aniyomi/util/DataSaver.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import okhttp3.OkHttpClient import okhttp3.Response -import rx.Observable import tachiyomi.core.preference.Preference import uy.kohesive.injekt.injectLazy @@ -27,15 +26,6 @@ interface DataSaver { } } - fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable { - val imageUrl = page.imageUrl ?: return fetchImage(page) - page.imageUrl = dataSaver.compress(imageUrl) - return fetchImage(page) - .doOnNext { - page.imageUrl = imageUrl - } - } - suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response { val imageUrl = page.imageUrl ?: return getImage(page) page.imageUrl = dataSaver.compress(imageUrl) diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index c3b8786ee..d9e02fd31 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -77,6 +77,7 @@ import tachiyomi.domain.category.manga.interactor.SetMangaDisplayMode import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory import tachiyomi.domain.category.manga.interactor.UpdateMangaCategory import tachiyomi.domain.category.manga.repository.MangaCategoryRepository +import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes @@ -85,17 +86,16 @@ import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags -import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval import tachiyomi.domain.entries.anime.repository.AnimeRepository import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.GetMangaFavorites import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters +import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags -import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval import tachiyomi.domain.entries.manga.repository.MangaRepository import tachiyomi.domain.history.anime.interactor.GetAnimeHistory import tachiyomi.domain.history.anime.interactor.GetNextEpisodes @@ -188,7 +188,7 @@ class DomainModule : InjektModule { addFactory { GetNextEpisodes(get(), get(), get()) } addFactory { ResetAnimeViewerFlags(get()) } addFactory { SetAnimeEpisodeFlags(get()) } - addFactory { SetAnimeFetchInterval(get()) } + addFactory { AnimeFetchInterval(get()) } addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) } addFactory { SetAnimeViewerFlags(get()) } addFactory { NetworkToLocalAnime(get()) } @@ -204,7 +204,7 @@ class DomainModule : InjektModule { addFactory { GetNextChapters(get(), get(), get()) } addFactory { ResetMangaViewerFlags(get()) } addFactory { SetMangaChapterFlags(get()) } - addFactory { SetMangaFetchInterval(get()) } + addFactory { MangaFetchInterval(get()) } addFactory { SetMangaDefaultChapterFlags( get(), diff --git a/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt b/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt index cba3f1d6e..dbb935b6b 100644 --- a/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt +++ b/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt @@ -3,7 +3,7 @@ package eu.kanade.domain.entries.anime.interactor import eu.kanade.domain.entries.anime.model.hasCustomCover import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.data.cache.AnimeCoverCache -import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval +import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.AnimeUpdate import tachiyomi.domain.entries.anime.repository.AnimeRepository @@ -15,7 +15,7 @@ import java.util.Date class UpdateAnime( private val animeRepository: AnimeRepository, - private val setAnimeFetchInterval: SetAnimeFetchInterval, + private val animeFetchInterval: AnimeFetchInterval, ) { suspend fun await(animeUpdate: AnimeUpdate): Boolean { @@ -79,9 +79,9 @@ class UpdateAnime( suspend fun awaitUpdateFetchInterval( anime: Anime, dateTime: ZonedDateTime = ZonedDateTime.now(), - window: Pair = setAnimeFetchInterval.getWindow(dateTime), + window: Pair = animeFetchInterval.getWindow(dateTime), ): Boolean { - return setAnimeFetchInterval.toAnimeUpdateOrNull(anime, dateTime, window) + return animeFetchInterval.toAnimeUpdateOrNull(anime, dateTime, window) ?.let { animeRepository.updateAnime(it) } ?: false } diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt index fe623028f..9e2058e9b 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt @@ -3,7 +3,7 @@ package eu.kanade.domain.entries.manga.interactor import eu.kanade.domain.entries.manga.model.hasCustomCover import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.source.model.SManga -import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval +import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.MangaUpdate import tachiyomi.domain.entries.manga.repository.MangaRepository @@ -15,7 +15,7 @@ import java.util.Date class UpdateManga( private val mangaRepository: MangaRepository, - private val setMangaFetchInterval: SetMangaFetchInterval, + private val mangaFetchInterval: MangaFetchInterval, ) { suspend fun await(mangaUpdate: MangaUpdate): Boolean { @@ -79,9 +79,9 @@ class UpdateManga( suspend fun awaitUpdateFetchInterval( manga: Manga, dateTime: ZonedDateTime = ZonedDateTime.now(), - window: Pair = setMangaFetchInterval.getWindow(dateTime), + window: Pair = mangaFetchInterval.getWindow(dateTime), ): Boolean { - return setMangaFetchInterval.toMangaUpdateOrNull(manga, dateTime, window) + return mangaFetchInterval.toMangaUpdateOrNull(manga, dateTime, window) ?.let { mangaRepository.updateManga(it) } ?: false } diff --git a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt index 5de3e963a..294812bdc 100644 --- a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt @@ -28,7 +28,7 @@ class UiPreferences( fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) - fun relativeTime() = preferenceStore.getInt("relative_time", 7) + fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true) fun dateFormat() = preferenceStore.getString("app_date_format", "") diff --git a/app/src/main/java/eu/kanade/presentation/components/RelativeDateHeader.kt b/app/src/main/java/eu/kanade/presentation/components/RelativeDateHeader.kt index 89bb61b4f..2466e2eed 100644 --- a/app/src/main/java/eu/kanade/presentation/components/RelativeDateHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/components/RelativeDateHeader.kt @@ -13,13 +13,18 @@ import java.util.Date fun RelativeDateHeader( modifier: Modifier = Modifier, date: Date, + relativeTime: Boolean, dateFormat: DateFormat, ) { val context = LocalContext.current ListGroupHeader( modifier = modifier, text = remember { - date.toRelativeString(context, dateFormat) + date.toRelativeString( + context, + relativeTime, + dateFormat, + ) }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt index 1c88c7476..ccb23d55e 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt @@ -159,7 +159,7 @@ fun EntryBottomActionMenu( val previousUnviewed = if (isManga) R.string.action_mark_previous_as_read else R.string.action_mark_previous_as_seen Button( title = stringResource(previousUnviewed), - icon = ImageVector.vectorResource(id = R.drawable.ic_done_prev_24dp), + icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp), toConfirm = confirm[4], onLongClick = { onLongClickItem(4) }, onClick = onMarkPreviousAsViewedClicked, diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index 3520fa0e4..6b2da30ef 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -98,6 +98,7 @@ fun AnimeScreen( state: AnimeScreenModel.State.Success, snackbarHostState: SnackbarHostState, fetchInterval: Int?, + dateRelativeTime: Boolean, dateFormat: DateFormat, isTabletUi: Boolean, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, @@ -161,6 +162,7 @@ fun AnimeScreen( AnimeScreenSmallImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, fetchInterval = fetchInterval, episodeSwipeStartAction = episodeSwipeStartAction, @@ -201,6 +203,7 @@ fun AnimeScreen( AnimeScreenLargeImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeEndAction = episodeSwipeEndAction, showNextEpisodeAirTime = showNextEpisodeAirTime, @@ -245,6 +248,7 @@ fun AnimeScreen( private fun AnimeScreenSmallImpl( state: AnimeScreenModel.State.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Boolean, dateFormat: DateFormat, fetchInterval: Int?, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, @@ -316,11 +320,9 @@ private fun AnimeScreenSmallImpl( } val animatedTitleAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0) 1f else 0f, - label = "titleAlpha", ) val animatedBgAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f, - label = "bgAlpha", ) EntryToolbar( title = state.anime.title, @@ -497,6 +499,7 @@ private fun AnimeScreenSmallImpl( sharedEpisodeItems( anime = state.anime, episodes = episodes, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeEndAction = episodeSwipeEndAction, @@ -516,6 +519,7 @@ private fun AnimeScreenSmallImpl( fun AnimeScreenLargeImpl( state: AnimeScreenModel.State.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Boolean, dateFormat: DateFormat, fetchInterval: Int?, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, @@ -762,6 +766,7 @@ fun AnimeScreenLargeImpl( sharedEpisodeItems( anime = state.anime, episodes = episodes, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeEndAction = episodeSwipeEndAction, @@ -832,6 +837,7 @@ private fun SharedAnimeBottomActionMenu( private fun LazyListScope.sharedEpisodeItems( anime: Anime, episodes: List, + dateRelativeTime: Boolean, dateFormat: DateFormat, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, @@ -860,7 +866,11 @@ private fun LazyListScope.sharedEpisodeItems( date = episodeItem.episode.dateUpload .takeIf { it > 0L } ?.let { - Date(it).toRelativeString(context, dateFormat) + Date(it).toRelativeString( + context, + dateRelativeTime, + dateFormat, + ) }, watchProgress = episodeItem.episode.lastSecondSeen .takeIf { !episodeItem.episode.seen && it > 0L } diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt index 76dd30900..02b564261 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt @@ -91,6 +91,7 @@ fun MangaScreen( state: MangaScreenModel.State.Success, snackbarHostState: SnackbarHostState, fetchInterval: Int?, + dateRelativeTime: Boolean, dateFormat: DateFormat, isTabletUi: Boolean, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, @@ -151,6 +152,7 @@ fun MangaScreen( MangaScreenSmallImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, fetchInterval = fetchInterval, chapterSwipeStartAction = chapterSwipeStartAction, @@ -188,6 +190,7 @@ fun MangaScreen( MangaScreenLargeImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, dateFormat = dateFormat, @@ -228,6 +231,7 @@ fun MangaScreen( private fun MangaScreenSmallImpl( state: MangaScreenModel.State.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Boolean, dateFormat: DateFormat, fetchInterval: Int?, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, @@ -297,11 +301,9 @@ private fun MangaScreenSmallImpl( } val animatedTitleAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0) 1f else 0f, - label = "titleAlpha", ) val animatedBgAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f, - label = "bgAlpha", ) EntryToolbar( title = state.manga.title, @@ -446,6 +448,7 @@ private fun MangaScreenSmallImpl( sharedChapterItems( manga = state.manga, chapters = chapters, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, @@ -464,6 +467,7 @@ private fun MangaScreenSmallImpl( fun MangaScreenLargeImpl( state: MangaScreenModel.State.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Boolean, dateFormat: DateFormat, fetchInterval: Int?, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, @@ -679,6 +683,7 @@ fun MangaScreenLargeImpl( sharedChapterItems( manga = state.manga, chapters = chapters, + dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, @@ -741,6 +746,7 @@ private fun SharedMangaBottomActionMenu( private fun LazyListScope.sharedChapterItems( manga: Manga, chapters: List, + dateRelativeTime: Boolean, dateFormat: DateFormat, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, @@ -769,7 +775,11 @@ private fun LazyListScope.sharedChapterItems( date = chapterItem.chapter.dateUpload .takeIf { it > 0L } ?.let { - Date(it).toRelativeString(context, dateFormat) + Date(it).toRelativeString( + context, + dateRelativeTime, + dateFormat, + ) }, readProgress = chapterItem.chapter.lastPageRead .takeIf { !chapterItem.chapter.read && it > 0L } diff --git a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryContent.kt b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryContent.kt index e8daba532..a828bc9ba 100644 --- a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryContent.kt @@ -13,7 +13,6 @@ import tachiyomi.domain.history.anime.model.AnimeHistoryWithRelations import tachiyomi.presentation.core.components.FastScrollLazyColumn import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.DateFormat @Composable fun AnimeHistoryContent( @@ -24,7 +23,8 @@ fun AnimeHistoryContent( onClickDelete: (AnimeHistoryWithRelations) -> Unit, preferences: UiPreferences = Injekt.get(), ) { - val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } + val relativeTime = remember { preferences.relativeTime().get() } + val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } FastScrollLazyColumn( contentPadding = contentPadding, @@ -44,6 +44,7 @@ fun AnimeHistoryContent( RelativeDateHeader( modifier = Modifier.animateItemPlacement(), date = item.date, + relativeTime = relativeTime, dateFormat = dateFormat, ) } diff --git a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryContent.kt b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryContent.kt index 2bf47c83a..ccf17e47b 100644 --- a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryContent.kt @@ -11,7 +11,6 @@ import tachiyomi.domain.history.manga.model.MangaHistoryWithRelations import tachiyomi.presentation.core.components.FastScrollLazyColumn import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.DateFormat @Composable fun MangaHistoryContent( @@ -22,7 +21,8 @@ fun MangaHistoryContent( onClickDelete: (MangaHistoryWithRelations) -> Unit, preferences: UiPreferences = Injekt.get(), ) { - val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } + val relativeTime = remember { preferences.relativeTime().get() } + val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } FastScrollLazyColumn( contentPadding = contentPadding, @@ -42,6 +42,7 @@ fun MangaHistoryContent( RelativeDateHeader( modifier = Modifier.animateItemPlacement(), date = item.date, + relativeTime = relativeTime, dateFormat = dateFormat, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt index d647b1750..43ec4bc5f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -72,7 +72,7 @@ fun MoreScreen( textRes = R.string.fdroid_warning, modifier = Modifier.clickable { uriHandler.openUri( - "https://aniyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version", + "https://aniyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds", ) }, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt index af369f557..85dd14956 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt @@ -129,6 +129,11 @@ object SettingsAppearanceScreen : SearchableSettings { } val now = remember { Date().time } + val dateFormat by uiPreferences.dateFormat().collectAsState() + val formattedNow = remember(dateFormat) { + UiPreferences.dateFormat(dateFormat).format(now) + } + LaunchedEffect(currentLanguage) { val locale = if (currentLanguage.isEmpty()) { LocaleListCompat.getEmptyLocaleList() @@ -199,6 +204,15 @@ object SettingsAppearanceScreen : SearchableSettings { "${it.ifEmpty { stringResource(R.string.label_default) }} ($formattedDate)" }, ), + Preference.PreferenceItem.SwitchPreference( + pref = uiPreferences.relativeTime(), + title = stringResource(R.string.pref_relative_format), + subtitle = stringResource( + R.string.pref_relative_format_summary, + stringResource(R.string.relative_time_today), + formattedNow, + ), + ), ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt index bac500b9d..697304ffe 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt @@ -149,7 +149,7 @@ object AboutScreen : Screen() { title = stringResource(R.string.help_translate), onPreferenceClick = { uriHandler.openUri( - "https://aniyomi.org/help/contribution/#translation", + "https://aniyomi.org/docs/contribute#translation", ) }, ) @@ -165,7 +165,7 @@ object AboutScreen : Screen() { item { TextPreferenceWidget( title = stringResource(R.string.privacy_policy), - onPreferenceClick = { uriHandler.openUri("https://aniyomi.org/privacy") }, + onPreferenceClick = { uriHandler.openUri("https://aniyomi.org/privacy/") }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt index 1de01f487..7c9646385 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt @@ -40,9 +40,9 @@ class OpenSourceLicensesScreen : Screen() { ), onLibraryClick = { val libraryLicenseScreen = OpenSourceLibraryLicenseScreen( - name = it.name, - website = it.website, - license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(), + name = it.library.name, + website = it.library.website, + license = it.library.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(), ) navigator.push(libraryLicenseScreen) }, diff --git a/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt index 8c53f0025..7455d2e73 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt @@ -1,25 +1,25 @@ package eu.kanade.presentation.reader -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.FilterChip -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import eu.kanade.domain.entries.manga.model.orientationType import eu.kanade.presentation.components.AdaptiveSheet import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import tachiyomi.presentation.core.components.SettingsChipRow -import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.components.SettingsIconGrid +import tachiyomi.presentation.core.components.material.IconToggleButton private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it } @@ -36,22 +36,20 @@ fun OrientationModeSelectDialog( ) } - AdaptiveSheet( - onDismissRequest = onDismissRequest, - ) { - Row( - modifier = Modifier.padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - ) { - SettingsChipRow(R.string.rotation_type) { - orientationTypeOptions.map { (stringRes, it) -> - FilterChip( - selected = it == orientationType, - onClick = { - screenModel.onChangeOrientation(it) + AdaptiveSheet(onDismissRequest = onDismissRequest) { + Box(modifier = Modifier.padding(vertical = 16.dp)) { + SettingsIconGrid(R.string.rotation_type) { + items(orientationTypeOptions) { (stringRes, mode) -> + IconToggleButton( + checked = mode == orientationType, + onCheckedChange = { + screenModel.onChangeOrientation(mode) onChange(stringRes) + onDismissRequest() }, - label = { Text(stringResource(stringRes)) }, + modifier = Modifier.fillMaxWidth(), + imageVector = ImageVector.vectorResource(mode.iconRes), + title = stringResource(stringRes), ) } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt index ced04a6f8..bd923995c 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt @@ -1,24 +1,25 @@ package eu.kanade.presentation.reader -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.FilterChip +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.vectorResource import eu.kanade.domain.entries.manga.model.readingModeType import eu.kanade.presentation.components.AdaptiveSheet import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType -import tachiyomi.presentation.core.components.SettingsChipRow +import tachiyomi.presentation.core.components.SettingsIconGrid +import tachiyomi.presentation.core.components.material.IconToggleButton import tachiyomi.presentation.core.components.material.padding private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it } @@ -36,22 +37,20 @@ fun ReadingModeSelectDialog( ) } - AdaptiveSheet( - onDismissRequest = onDismissRequest, - ) { - Row( - modifier = Modifier.padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - ) { - SettingsChipRow(R.string.pref_category_reading_mode) { - readingModeOptions.map { (stringRes, it) -> - FilterChip( - selected = it == readingMode, - onClick = { - screenModel.onChangeReadingMode(it) + AdaptiveSheet(onDismissRequest = onDismissRequest) { + Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) { + SettingsIconGrid(R.string.pref_category_reading_mode) { + items(readingModeOptions) { (stringRes, mode) -> + IconToggleButton( + checked = mode == readingMode, + onCheckedChange = { + screenModel.onChangeReadingMode(mode) onChange(stringRes) + onDismissRequest() }, - label = { Text(stringResource(stringRes)) }, + modifier = Modifier.fillMaxWidth(), + imageVector = ImageVector.vectorResource(mode.iconRes), + title = stringResource(stringRes), ) } } diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackServiceSearch.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackServiceSearch.kt index c223f7c62..a213017e7 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackServiceSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackServiceSearch.kt @@ -229,6 +229,7 @@ fun SearchResultItem( val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent Box( modifier = Modifier + .fillMaxWidth() .padding(horizontal = 12.dp) .clip(shape) .background(MaterialTheme.colorScheme.surface) diff --git a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt index f699f34a6..7166f22eb 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt @@ -38,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds fun AnimeUpdateScreen( state: AnimeUpdatesScreenModel.State, snackbarHostState: SnackbarHostState, + relativeTime: Boolean, contentPadding: PaddingValues, lastUpdated: Long, onClickCover: (AnimeUpdatesItem) -> Unit, @@ -100,7 +101,7 @@ fun AnimeUpdateScreen( animeUpdatesLastUpdatedItem(lastUpdated) } animeUpdatesUiItems( - uiModels = state.getUiModel(context), + uiModels = state.getUiModel(context, relativeTime), selectionMode = state.selectionMode, onUpdateSelected = onUpdateSelected, onClickCover = onClickCover, diff --git a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt index b68084aab..4e0da1d97 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt @@ -35,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds fun MangaUpdateScreen( state: MangaUpdatesScreenModel.State, snackbarHostState: SnackbarHostState, + relativeTime: Boolean, contentPadding: PaddingValues, lastUpdated: Long, onClickCover: (MangaUpdatesItem) -> Unit, @@ -97,7 +98,7 @@ fun MangaUpdateScreen( } mangaUpdatesUiItems( - uiModels = state.getUiModel(context), + uiModels = state.getUiModel(context, relativeTime), selectionMode = state.selectionMode, onUpdateSelected = onUpdateSelected, onClickCover = onClickCover, diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt index 42fdf9033..7bd3ad77d 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt @@ -176,7 +176,7 @@ fun WebViewScreenContent( textRes = R.string.information_cloudflare_help, modifier = Modifier.clickable { uriHandler.openUri( - "https://aniyomi.org/docs/guides/troubleshooting/#solving-cloudflare-issues", + "https://aniyomi.org/docs/guides/troubleshooting/#cloudflare", ) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index e7863c5cc..aa312be3e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -505,6 +505,12 @@ object Migrations { pref.getAndSet { it - "battery_not_low" } } } + if (oldVersion < 106) { + val pref = preferenceStore.getInt("relative_time", 7) + if (pref.get() == 0) { + uiPreferences.relativeTime().set(false) + } + } return true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt index 0b2d6b751..44e22adbf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt @@ -30,9 +30,9 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive import tachiyomi.core.util.system.logcat -import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval +import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval import tachiyomi.domain.entries.anime.model.Anime -import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval +import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.items.chapter.repository.ChapterRepository @@ -54,15 +54,15 @@ class BackupRestorer( ) { private val updateManga: UpdateManga = Injekt.get() private val chapterRepository: ChapterRepository = Injekt.get() - private val setMangaFetchInterval: SetMangaFetchInterval = Injekt.get() + private val mangaFetchInterval: MangaFetchInterval = Injekt.get() private val updateAnime: UpdateAnime = Injekt.get() private val episodeRepository: EpisodeRepository = Injekt.get() - private val setAnimeFetchInterval: SetAnimeFetchInterval = Injekt.get() + private val animeFetchInterval: AnimeFetchInterval = Injekt.get() private var now = ZonedDateTime.now() - private var currentMangaFetchWindow = setMangaFetchInterval.getWindow(now) - private var currentAnimeFetchWindow = setAnimeFetchInterval.getWindow(now) + private var currentMangaFetchWindow = mangaFetchInterval.getWindow(now) + private var currentAnimeFetchWindow = animeFetchInterval.getWindow(now) private var backupManager = BackupManager(context) @@ -152,8 +152,8 @@ class BackupRestorer( animeSourceMapping = backupAnimeMaps.associate { it.sourceId to it.name } now = ZonedDateTime.now() - currentMangaFetchWindow = setMangaFetchInterval.getWindow(now) - currentAnimeFetchWindow = setAnimeFetchInterval.getWindow(now) + currentMangaFetchWindow = mangaFetchInterval.getWindow(now) + currentAnimeFetchWindow = animeFetchInterval.getWindow(now) return coroutineScope { // Restore individual manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt index 413f69d58..2493bdf7a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt @@ -674,7 +674,7 @@ class AnimeDownloader( if (isHls(video) || isMpd(video)) { return ffmpegDownload(video, download, tmpDir, filename) } else { - val response = download.source.fetchVideo(video) + val response = download.source.getVideo(video) val file = tmpDir.findFile("$filename.tmp") ?: tmpDir.createFile("$filename.tmp") // Write to file with pause/resume capability diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 4bf7ddbcc..8d889cedd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -46,7 +46,6 @@ import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE import tachiyomi.core.metadata.comicinfo.ComicInfo -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.withIOContext @@ -396,7 +395,7 @@ class MangaDownloader( if (page.imageUrl.isNullOrEmpty()) { page.status = Page.State.LOAD_PAGE try { - page.imageUrl = download.source.fetchImageUrl(page).awaitSingle() + page.imageUrl = download.source.getImageUrl(page) } catch (e: Throwable) { page.status = Page.State.ERROR } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt index 7d199f72b..d12c429e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt @@ -48,9 +48,9 @@ import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.model.Category import tachiyomi.domain.download.service.DownloadPreferences +import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime -import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.toAnimeUpdate import tachiyomi.domain.items.episode.model.Episode @@ -90,7 +90,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa private val getCategories: GetAnimeCategories = Injekt.get() private val syncEpisodesWithSource: SyncEpisodesWithSource = Injekt.get() private val refreshAnimeTracks: RefreshAnimeTracks = Injekt.get() - private val setAnimeFetchInterval: SetAnimeFetchInterval = Injekt.get() + private val animeFetchInterval: AnimeFetchInterval = Injekt.get() private val notifier = AnimeLibraryUpdateNotifier(context) @@ -216,7 +216,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa val failedUpdates = CopyOnWriteArrayList>() val hasDownloads = AtomicBoolean(false) val restrictions = libraryPreferences.autoUpdateItemRestrictions().get() - val fetchWindow = setAnimeFetchInterval.getWindow(ZonedDateTime.now()) + val fetchWindow = animeFetchInterval.getWindow(ZonedDateTime.now()) coroutineScope { animeToUpdate.groupBy { it.anime.source }.values @@ -537,7 +537,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa private const val WORK_NAME_AUTO = "AnimeLibraryUpdate-auto" private const val WORK_NAME_MANUAL = "AnimeLibraryUpdate-manual" - private const val ERROR_LOG_HELP_URL = "https://aniyomi.org/help/guides/troubleshooting" + private const val ERROR_LOG_HELP_URL = "https://aniyomi.org/docs/guides/troubleshooting/" private const val ANIME_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt index 2c79d6a21..4566485cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt @@ -377,7 +377,7 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { companion object { // TODO: Change when implemented on Aniyomi website - const val HELP_WARNING_URL = "https://aniyomi.org/help/faq/#why-does-the-app-warn-about-large-bulk-updates-and-downloads" + const val HELP_WARNING_URL = "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" } } @@ -386,4 +386,4 @@ private const val NOTIF_ANIME_TITLE_MAX_LEN = 45 private const val NOTIF_ANIME_ICON_SIZE = 192 // TODO: Change when implemented on Aniyomi website -private const val HELP_SKIPPED_ANIME_URL = "https://aniyomi.org/help/faq/#why-does-global-update-skip-some-entries" +private const val HELP_SKIPPED_ANIME_URL = "https://aniyomi.org/docs/faq/library#why-is-global-update-skipping-entries" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt index eb2ff294f..18edc3e41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt @@ -50,7 +50,7 @@ import tachiyomi.domain.category.model.Category import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.entries.manga.interactor.GetManga -import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval +import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.toMangaUpdate import tachiyomi.domain.items.chapter.model.Chapter @@ -90,7 +90,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa private val getCategories: GetMangaCategories = Injekt.get() private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get() private val refreshMangaTracks: RefreshMangaTracks = Injekt.get() - private val setMangaFetchInterval: SetMangaFetchInterval = Injekt.get() + private val mangaFetchInterval: MangaFetchInterval = Injekt.get() private val notifier = MangaLibraryUpdateNotifier(context) @@ -216,7 +216,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa val failedUpdates = CopyOnWriteArrayList>() val hasDownloads = AtomicBoolean(false) val restrictions = libraryPreferences.autoUpdateItemRestrictions().get() - val fetchWindow = setMangaFetchInterval.getWindow(ZonedDateTime.now()) + val fetchWindow = mangaFetchInterval.getWindow(ZonedDateTime.now()) coroutineScope { mangaToUpdate.groupBy { it.manga.source }.values diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt index 0a318db05..a8745dc4a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt @@ -380,7 +380,7 @@ class MangaLibraryUpdateNotifier(private val context: Context) { companion object { // TODO: Change when implemented on Aniyomi website - const val HELP_WARNING_URL = "https://aniyomi.org/help/faq/#why-does-the-app-warn-about-large-bulk-updates-and-downloads" + const val HELP_WARNING_URL = "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" } } @@ -389,4 +389,4 @@ private const val NOTIF_MANGA_TITLE_MAX_LEN = 45 private const val NOTIF_MANGA_ICON_SIZE = 192 // TODO: Change when implemented on Aniyomi website -private const val HELP_SKIPPED_MANGA_URL = "https://aniyomi.org/help/faq/#why-does-global-update-skip-some-entries" +private const val HELP_SKIPPED_MANGA_URL = "https://aniyomi.org/docs/faq/library#why-is-global-update-skipping-entries" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index 4d3d564c2..60cb064e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -32,28 +32,56 @@ class ImageSaver( fun save(image: Image): Uri { val data = image.data - val type = ImageUtil.findImageType(data) ?: throw Exception("Not an image") + val type = ImageUtil.findImageType(data) ?: throw IllegalArgumentException("Not an image") val filename = DiskUtil.buildValidFilename("${image.name}.${type.extension}") if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || image.location !is Location.Pictures) { return save(data(), image.location.directory(context), filename) } + return saveApi29(image, type, filename, data) + } + + private fun save(inputStream: InputStream, directory: File, filename: String): Uri { + directory.mkdirs() + + val destFile = File(directory, filename) + + inputStream.use { input -> + destFile.outputStream().use { output -> + input.copyTo(output) + } + } + + DiskUtil.scanMedia(context, destFile.toUri()) + + return destFile.getUriCompat(context) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun saveApi29( + image: Image, + type: ImageUtil.ImageType, + filename: String, + data: () -> InputStream, + ): Uri { val pictureDir = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) - val folderRelativePath = "${Environment.DIRECTORY_PICTURES}/${context.getString( - R.string.app_name, - )}/" val imageLocation = (image.location as Location.Pictures).relativePath + val relativePath = listOf( + Environment.DIRECTORY_PICTURES, + context.getString(R.string.app_name), + imageLocation, + ).joinToString(File.separator) val contentValues = contentValuesOf( + MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.Images.Media.MIME_TYPE to type.mime, - MediaStore.Images.Media.RELATIVE_PATH to folderRelativePath + imageLocation, ) - val picture = findUriOrDefault(folderRelativePath, "$imageLocation$filename") { + val picture = findUriOrDefault(relativePath, filename) { context.contentResolver.insert( pictureDir, contentValues, @@ -76,24 +104,8 @@ class ImageSaver( return picture } - private fun save(inputStream: InputStream, directory: File, filename: String): Uri { - directory.mkdirs() - - val destFile = File(directory, filename) - - inputStream.use { input -> - destFile.outputStream().use { output -> - input.copyTo(output) - } - } - - DiskUtil.scanMedia(context, destFile.toUri()) - - return destFile.getUriCompat(context) - } - @RequiresApi(Build.VERSION_CODES.Q) - private fun findUriOrDefault(relativePath: String, imagePath: String, default: () -> Uri): Uri { + private fun findUriOrDefault(path: String, filename: String, default: () -> Uri): Uri { val projection = arrayOf( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME, @@ -104,19 +116,19 @@ class ImageSaver( val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}=? AND ${MediaStore.MediaColumns.DISPLAY_NAME}=?" + // Need to make sure it ends with the separator + val normalizedPath = "${path.removeSuffix(File.separator)}${File.separator}" + context.contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, - arrayOf(relativePath, imagePath), + arrayOf(normalizedPath, filename), null, ).use { cursor -> if (cursor != null && cursor.count >= 1) { - cursor.moveToFirst().let { - val id = cursor.getLong( - cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID), - ) - + if (cursor.moveToFirst()) { + val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) return ContentUris.withAppendedId( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id, @@ -124,6 +136,7 @@ class ImageSaver( } } } + return default() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt index a68b5c94d..74138bce8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt @@ -161,7 +161,7 @@ internal class AppUpdateNotifier(private val context: Context) { setContentIntent( NotificationHandler.openUrl( context, - "https://aniyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version", + "https://aniyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds", ), ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreenModel.kt index 5bc9f0ab4..58bfbc95c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreenModel.kt @@ -105,7 +105,7 @@ class AnimeExtensionDetailsScreenModel( val extension = state.value.extension ?: return "" if (!extension.hasReadme) { - return "https://aniyomi.org/help/faq/#extensions" + return "https://aniyomi.org/docs/faq/browse/extensions" } val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.animeextension.") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt index 49a6231f2..06a7967af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt @@ -31,10 +31,8 @@ import eu.kanade.tachiyomi.util.removeCovers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -115,25 +113,20 @@ class BrowseAnimeSourceScreenModel( /** * Flow of Pager flow tied to [State.listing] */ + private val hideInLibraryItems = sourcePreferences.hideInAnimeLibraryItems().get() val animePagerFlowFlow = state.map { it.listing } .distinctUntilChanged() .map { listing -> - Pager( - PagingConfig(pageSize = 25), - ) { + Pager(PagingConfig(pageSize = 25)) { getRemoteAnime.subscribe(sourceId, listing.query ?: "", listing.filters) }.flow.map { pagingData -> pagingData.map { networkToLocalAnime.await(it.toDomainAnime(sourceId)) - .let { localAnime -> - getAnime.subscribe(localAnime.url, localAnime.source) - } + .let { localAnime -> getAnime.subscribe(localAnime.url, localAnime.source) } .filterNotNull() - .filter { localAnime -> - !sourcePreferences.hideInAnimeLibraryItems().get() || !localAnime.favorite - } .stateIn(ioCoroutineScope) } + .filter { !hideInLibraryItems || !it.value.favorite } } .cachedIn(ioCoroutineScope) } @@ -301,7 +294,6 @@ class BrowseAnimeSourceScreenModel( track.anime_id = anime.id (service as TrackService).animeService.bind(track) insertTrack.await(track.toDomainTrack()!!) - syncEpisodeProgressWithTrack.await( anime.id, track.toDomainTrack()!!, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreenModel.kt index e7998ce94..2a2ba1cb1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreenModel.kt @@ -105,7 +105,7 @@ class MangaExtensionDetailsScreenModel( val extension = state.value.extension ?: return "" if (!extension.hasReadme) { - return "https://aniyomi.org/help/faq/#extensions" + return "https://aniyomi.org/docs/faq/browse/extensions" } val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt index 5ea5186de..8e00d2443 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.unit.dp import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn +import androidx.paging.filter import androidx.paging.map import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope @@ -30,7 +31,6 @@ import eu.kanade.tachiyomi.util.removeCovers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -113,25 +113,20 @@ class BrowseMangaSourceScreenModel( /** * Flow of Pager flow tied to [State.listing] */ + private val hideInLibraryItems = sourcePreferences.hideInMangaLibraryItems().get() val mangaPagerFlowFlow = state.map { it.listing } .distinctUntilChanged() .map { listing -> - Pager( - PagingConfig(pageSize = 25), - ) { + Pager(PagingConfig(pageSize = 25)) { getRemoteManga.subscribe(sourceId, listing.query ?: "", listing.filters) }.flow.map { pagingData -> pagingData.map { networkToLocalManga.await(it.toDomainManga(sourceId)) - .let { localManga -> - getManga.subscribe(localManga.url, localManga.source) - } + .let { localManga -> getManga.subscribe(localManga.url, localManga.source) } .filterNotNull() - .filter { localManga -> - !sourcePreferences.hideInMangaLibraryItems().get() || !localManga.favorite - } .stateIn(ioCoroutineScope) } + .filter { !hideInLibraryItems || !it.value.favorite } } .cachedIn(ioCoroutineScope) } @@ -301,7 +296,6 @@ class BrowseMangaSourceScreenModel( track.manga_id = manga.id (service as TrackService).mangaService.bind(track) insertTrack.await(track.toDomainTrack()!!) - syncChapterProgressWithTrack.await( manga.id, track.toDomainTrack()!!, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt index 0511c0ebe..85980f98a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.model.Manga @@ -138,7 +137,7 @@ abstract class MangaSearchScreenModel( } try { val page = withContext(coroutineDispatcher) { - source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle() + source.getSearchManga(1, query, source.getFilterList()) } val titles = page.mangas.map { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt index 8eaffb5c2..61a6aeae5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreenModel.kt @@ -78,13 +78,17 @@ class AnimeDownloadQueueScreenModel( } reorder(newAnimeDownloads) } - R.id.move_to_top_series -> { + R.id.move_to_top_series, R.id.move_to_bottom_series -> { val (selectedSeries, otherSeries) = adapter?.currentItems ?.filterIsInstance() ?.map(AnimeDownloadItem::download) ?.partition { item.download.anime.id == it.anime.id } ?: Pair(emptyList(), emptyList()) - reorder(selectedSeries + otherSeries) + if (menuItem.itemId == R.id.move_to_top_series) { + reorder(selectedSeries + otherSeries) + } else { + reorder(otherSeries + selectedSeries) + } } R.id.cancel_download -> { cancel(listOf(item.download)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt index 6a41d5f7f..a866ba778 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt @@ -84,13 +84,17 @@ class MangaDownloadQueueScreenModel( } reorder(newDownloads) } - R.id.move_to_top_series -> { + R.id.move_to_top_series, R.id.move_to_bottom_series -> { val (selectedSeries, otherSeries) = adapter?.currentItems ?.filterIsInstance() ?.map(MangaDownloadItem::download) ?.partition { item.download.manga.id == it.manga.id } ?: Pair(emptyList(), emptyList()) - reorder(selectedSeries + otherSeries) + if (menuItem.itemId == R.id.move_to_top_series) { + reorder(selectedSeries + otherSeries) + } else { + reorder(otherSeries + selectedSeries) + } } R.id.cancel_download -> { cancel(listOf(item.download)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt index cc4fd02ac..b2dacd35a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt @@ -105,6 +105,7 @@ class AnimeScreen( AnimeScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, + dateRelativeTime = screenModel.relativeTime, dateFormat = screenModel.dateFormat, fetchInterval = successState.anime.fetchInterval, isTabletUi = isTabletUi(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index 2a34ea110..615301a91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.core.preference.asState import eu.kanade.core.util.addOrRemove import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags import eu.kanade.domain.entries.anime.interactor.UpdateAnime @@ -132,6 +133,7 @@ class AnimeScreenModel( val alwaysUseExternalPlayer = playerPreferences.alwaysUseExternalPlayer().get() val useExternalDownloader = downloadPreferences.useExternalDownloader().get() + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val isUpdateIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateItemRestrictions().get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt index 8142955c0..9e42d892d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt @@ -100,6 +100,7 @@ class MangaScreen( MangaScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, + dateRelativeTime = screenModel.relativeTime, dateFormat = screenModel.dateFormat, fetchInterval = successState.manga.fetchInterval, isTabletUi = isTabletUi(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index d62c03198..115cc2e86 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -127,6 +127,7 @@ class MangaScreenModel( val chapterSwipeStartAction = libraryPreferences.swipeChapterEndAction().get() val chapterSwipeEndAction = libraryPreferences.swipeChapterStartAction().get() + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt index bebac791f..912de9071 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt @@ -198,7 +198,7 @@ object AnimeLibraryTab : Tab() { icon = Icons.Outlined.HelpOutline, onClick = { handler.openUri( - "https://aniyomi.org/help/guides/getting-started", + "https://aniyomi.org/docs/guides/getting-started", ) }, ), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt index 317b0181a..2236dfd01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt @@ -193,7 +193,7 @@ object MangaLibraryTab : Tab() { icon = Icons.Outlined.HelpOutline, onClick = { handler.openUri( - "https://aniyomi.org/help/guides/getting-started", + "https://aniyomi.org/docs/guides/getting-started", ) }, ), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt index b6203d5fd..64ab6291f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt @@ -340,7 +340,7 @@ class PlayerActivity : BaseActivity() { displayMode = state.anime!!.displayMode, episodeList = viewModel.currentPlaylist, currentEpisodeIndex = viewModel.getCurrentEpisodeIndex(), - relativeTime = viewModel.relativeTime, + dateRelativeTime = viewModel.relativeTime, dateFormat = viewModel.dateFormat, onBookmarkClicked = viewModel::bookmarkEpisode, onEpisodeClicked = this::changeEpisode, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/EpisodeListDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/EpisodeListDialog.kt index a35815582..171006525 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/EpisodeListDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/EpisodeListDialog.kt @@ -50,7 +50,7 @@ fun EpisodeListDialog( displayMode: Long, currentEpisodeIndex: Int, episodeList: List, - relativeTime: Int, + dateRelativeTime: Boolean, dateFormat: DateFormat, onBookmarkClicked: (Long?, Boolean) -> Unit, onEpisodeClicked: (Long?) -> Unit, @@ -92,7 +92,7 @@ fun EpisodeListDialog( val date = episode.date_upload .takeIf { it > 0L } ?.let { - Date(it).toRelativeString(context, dateFormat) + Date(it).toRelativeString(context, dateRelativeTime, dateFormat) } ?: "" EpisodeListItem( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index b4359a415..666d46edd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.suspendCancellableCoroutine -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.Injekt @@ -186,7 +185,7 @@ internal class HttpPageLoader( try { if (page.imageUrl.isNullOrEmpty()) { page.status = Page.State.LOAD_PAGE - page.imageUrl = source.fetchImageUrl(page).awaitSingle() + page.imageUrl = source.getImageUrl(page) } val imageUrl = page.imageUrl!! diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt index 66dc2b88b..06fa6be83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt @@ -60,6 +60,7 @@ class AnimeUpdatesScreenModel( private val getEpisode: GetEpisode = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), + uiPreferences: UiPreferences = Injekt.get(), downloadPreferences: DownloadPreferences = Injekt.get(), ) : StateScreenModel(State()) { @@ -67,6 +68,7 @@ class AnimeUpdatesScreenModel( val events: Flow = _events.receiveAsFlow() val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(coroutineScope) + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) val useExternalDownloader = downloadPreferences.useExternalDownloader().get() @@ -393,7 +395,7 @@ class AnimeUpdatesScreenModel( val selected = items.filter { it.selected } val selectionMode = selected.isNotEmpty() - fun getUiModel(context: Context): List { + fun getUiModel(context: Context, relativeTime: Boolean): List { val dateFormat by mutableStateOf( UiPreferences.dateFormat(Injekt.get().dateFormat().get()), ) @@ -405,7 +407,11 @@ class AnimeUpdatesScreenModel( val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) when { beforeDate.time != afterDate.time && afterDate.time != 0L -> { - val text = afterDate.toRelativeString(context, dateFormat) + val text = afterDate.toRelativeString( + context = context, + relative = relativeTime, + dateFormat = dateFormat, + ) AnimeUpdatesUiModel.Header(text) } // Return null to avoid adding a separator between two items. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt index bfcd915f3..ff77c7be4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt @@ -59,6 +59,7 @@ fun Screen.animeUpdatesTab( snackbarHostState = screenModel.snackbarHostState, contentPadding = contentPadding, lastUpdated = screenModel.lastUpdated, + relativeTime = screenModel.relativeTime, onClickCover = { item -> navigator.push(AnimeScreen(item.update.animeId)) }, onSelectAll = screenModel::toggleAllSelection, onInvertSelection = screenModel::invertSelection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt index 5f713bc61..fb0d96c5c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt @@ -59,12 +59,14 @@ class MangaUpdatesScreenModel( private val getChapter: GetChapter = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), + uiPreferences: UiPreferences = Injekt.get(), ) : StateScreenModel(State()) { private val _events: Channel = Channel(Int.MAX_VALUE) val events: Flow = _events.receiveAsFlow() val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(coroutineScope) + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) // First and last selected index in list private val selectedPositions: Array = arrayOf(-1, -1) @@ -374,7 +376,7 @@ class MangaUpdatesScreenModel( val selected = items.filter { it.selected } val selectionMode = selected.isNotEmpty() - fun getUiModel(context: Context): List { + fun getUiModel(context: Context, relativeTime: Boolean): List { val dateFormat by mutableStateOf( UiPreferences.dateFormat(Injekt.get().dateFormat().get()), ) @@ -386,7 +388,11 @@ class MangaUpdatesScreenModel( val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) when { beforeDate.time != afterDate.time && afterDate.time != 0L -> { - val text = afterDate.toRelativeString(context, dateFormat) + val text = afterDate.toRelativeString( + context = context, + relative = relativeTime, + dateFormat = dateFormat, + ) MangaUpdatesUiModel.Header(text) } // Return null to avoid adding a separator between two items. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt index 07289f866..a458a0ffd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt @@ -46,6 +46,7 @@ fun Screen.mangaUpdatesTab( snackbarHostState = screenModel.snackbarHostState, contentPadding = contentPadding, lastUpdated = screenModel.lastUpdated, + relativeTime = screenModel.relativeTime, onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) }, onSelectAll = screenModel::toggleAllSelection, onInvertSelection = screenModel::invertSelection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index a3f8fe177..ecd8b8b7a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -4,6 +4,7 @@ import android.content.Context import android.os.Build import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.util.storage.getUriCompat +import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast @@ -35,6 +36,7 @@ class CrashLogUtil(private val context: Context) { Device name: ${Build.DEVICE} Device model: ${Build.MODEL} Device product name: ${Build.PRODUCT} + WebView user agent: ${WebViewUtil.getInferredUserAgent(context)} """.trimIndent() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt index 5c7a9039c..e2f683685 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt @@ -8,7 +8,6 @@ import java.time.LocalDateTime import java.time.ZoneId import java.util.Calendar import java.util.Date -import java.util.TimeZone fun Date.toDateTimestampString(dateFormatter: DateFormat): String { val date = dateFormatter.format(this) @@ -46,80 +45,18 @@ fun Long.toDateKey(): Date { return cal.time } -/** - * Convert epoch long to Calendar instance - * - * @return Calendar instance at supplied epoch time. Null if epoch was 0. - */ -fun Long.toCalendar(): Calendar? { - if (this == 0L) { - return null - } - val cal = Calendar.getInstance() - cal.timeInMillis = this - return cal -} - -/** - * Convert local time millisecond value to Calendar instance in UTC - * - * @return UTC Calendar instance at supplied time. Null if time is 0. - */ -fun Long.toUtcCalendar(): Calendar? { - if (this == 0L) { - return null - } - val rawCalendar = Calendar.getInstance().apply { - timeInMillis = this@toUtcCalendar - } - return Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { - clear() - set( - rawCalendar.get(Calendar.YEAR), - rawCalendar.get(Calendar.MONTH), - rawCalendar.get(Calendar.DAY_OF_MONTH), - rawCalendar.get(Calendar.HOUR_OF_DAY), - rawCalendar.get(Calendar.MINUTE), - rawCalendar.get(Calendar.SECOND), - ) - } -} - -/** - * Convert UTC time millisecond to Calendar instance in local time zone - * - * @return local Calendar instance at supplied UTC time. Null if time is 0. - */ -fun Long.toLocalCalendar(): Calendar? { - if (this == 0L) { - return null - } - val rawCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { - timeInMillis = this@toLocalCalendar - } - return Calendar.getInstance().apply { - clear() - set( - rawCalendar.get(Calendar.YEAR), - rawCalendar.get(Calendar.MONTH), - rawCalendar.get(Calendar.DAY_OF_MONTH), - rawCalendar.get(Calendar.HOUR_OF_DAY), - rawCalendar.get(Calendar.MINUTE), - rawCalendar.get(Calendar.SECOND), - ) - } -} - private const val MILLISECONDS_IN_DAY = 86_400_000L fun Date.toRelativeString( context: Context, + relative: Boolean = true, dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT), ): String { + if (!relative) { + return dateFormat.format(this) + } val now = Date() - val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest( - MILLISECONDS_IN_DAY, - ) + val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt() return when { difference < 0 -> dateFormat.format(this) diff --git a/app/src/main/res/menu/download_single.xml b/app/src/main/res/menu/download_single.xml index 34ec33a4d..1cf14ff0c 100644 --- a/app/src/main/res/menu/download_single.xml +++ b/app/src/main/res/menu/download_single.xml @@ -13,6 +13,10 @@ android:id="@+id/move_to_bottom" android:title="@string/action_move_to_bottom" /> + + diff --git a/core/src/main/java/eu/kanade/tachiyomi/core/Constants.kt b/core/src/main/java/eu/kanade/tachiyomi/core/Constants.kt index f829cb7da..98f8ae2f4 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/core/Constants.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/core/Constants.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.core object Constants { - const val URL_HELP = "https://aniyomi.org/help/" + const val URL_HELP = "https://aniyomi.org/docs/guides/troubleshooting/" const val MANGA_EXTRA = "manga" diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 4cbb34812..e6eec02f4 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -58,6 +58,15 @@ fun Call.asObservable(): Observable { } } +fun Call.asObservableSuccess(): Observable { + return asObservable().doOnNext { response -> + if (!response.isSuccessful) { + response.close() + throw HttpException(response.code) + } + } +} + // Based on https://github.com/gildor/kotlin-coroutines-okhttp @OptIn(ExperimentalCoroutinesApi::class) private suspend fun Call.await(callStack: Array): Response { @@ -95,6 +104,9 @@ suspend fun Call.await(): Response { return await(callStack) } +/** + * @since extensions-lib 1.5 + */ suspend fun Call.awaitSuccess(): Response { val callStack = Exception().stackTrace.run { copyOfRange(1, size) } val response = await(callStack) @@ -105,15 +117,6 @@ suspend fun Call.awaitSuccess(): Response { return response } -fun Call.asObservableSuccess(): Observable { - return asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - throw HttpException(response.code) - } - } -} - fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call { val progressClient = newBuilder() .cache(null) diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index 4fcaaceed..eb1ad9617 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -14,7 +14,24 @@ import kotlin.coroutines.resume object WebViewUtil { const val SPOOF_PACKAGE_NAME = "org.chromium.chrome" - const val MINIMUM_WEBVIEW_VERSION = 111 + const val MINIMUM_WEBVIEW_VERSION = 114 + + /** + * Uses the WebView's user agent string to create something similar to what Chrome on Android + * would return. + * + * Example of WebView user agent string: + * Mozilla/5.0 (Linux; Android 13; Pixel 7 Build/TQ3A.230901.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 + * + * Example of Chrome on Android: + * Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.3 + */ + fun getInferredUserAgent(context: Context): String { + return WebView(context) + .getDefaultUserAgentString() + .replace("; Android .*?\\)".toRegex(), "; Android 10; K)") + .replace("Version/.* Chrome/".toRegex(), "Chrome/") + } fun supportsWebView(context: Context): Boolean { try { diff --git a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/anime/AnimeSourcePagingSource.kt index ac356ea2a..f181e5e6e 100644 --- a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/anime/AnimeSourcePagingSource.kt @@ -16,18 +16,21 @@ class AnimeSourceSearchPagingSource( val filters: AnimeFilterList, ) : AnimeSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): AnimesPage { + // Replace with getSearchAnime return source.fetchSearchAnime(currentPage, query, filters).awaitSingle() } } class AnimeSourcePopularPagingSource(source: AnimeCatalogueSource) : AnimeSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): AnimesPage { + // Replace with getPopularAnime return source.fetchPopularAnime(currentPage).awaitSingle() } } class AnimeSourceLatestPagingSource(source: AnimeCatalogueSource) : AnimeSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): AnimesPage { + // Replace with getLatestUpdates return source.fetchLatestUpdates(currentPage).awaitSingle() } } diff --git a/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt index 6d21737a3..6ae08e274 100644 --- a/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.SManga -import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.withIOContext import tachiyomi.domain.items.chapter.model.NoChaptersException import tachiyomi.domain.source.manga.repository.SourcePagingSourceType @@ -14,19 +13,19 @@ class SourceSearchPagingSource(source: CatalogueSource, val query: String, val f source, ) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchSearchManga(currentPage, query, filters).awaitSingle() + return source.getSearchManga(currentPage, query, filters) } } class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchPopularManga(currentPage).awaitSingle() + return source.getPopularManga(currentPage) } } class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source.fetchLatestUpdates(currentPage).awaitSingle() + return source.getLatestUpdates(currentPage) } } diff --git a/domain/src/main/java/tachiyomi/domain/entries/anime/interactor/SetAnimeFetchInterval.kt b/domain/src/main/java/tachiyomi/domain/entries/anime/interactor/AnimeFetchInterval.kt similarity index 83% rename from domain/src/main/java/tachiyomi/domain/entries/anime/interactor/SetAnimeFetchInterval.kt rename to domain/src/main/java/tachiyomi/domain/entries/anime/interactor/AnimeFetchInterval.kt index 5855adc31..0ef833c59 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/anime/interactor/SetAnimeFetchInterval.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/anime/interactor/AnimeFetchInterval.kt @@ -4,16 +4,13 @@ import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.AnimeUpdate import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId import tachiyomi.domain.items.episode.model.Episode -import uy.kohesive.injekt.api.get import java.time.Instant +import java.time.ZoneId import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import kotlin.math.absoluteValue -const val MAX_FETCH_INTERVAL = 28 -private const val FETCH_INTERVAL_GRACE_PERIOD = 1 - -class SetAnimeFetchInterval( +class AnimeFetchInterval( private val getEpisodeByAnimeId: GetEpisodeByAnimeId, ) { @@ -30,7 +27,7 @@ class SetAnimeFetchInterval( val episodes = getEpisodeByAnimeId.await(anime.id) val interval = anime.fetchInterval.takeIf { it < 0 } ?: calculateInterval( episodes, - dateTime, + dateTime.zone, ) val nextUpdate = calculateNextUpdate(anime, interval, dateTime, currentWindow) @@ -43,33 +40,34 @@ class SetAnimeFetchInterval( fun getWindow(dateTime: ZonedDateTime): Pair { val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone) - val lowerBound = today.minusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong()) - val upperBound = today.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong()) + val lowerBound = today.minusDays(GRACE_PERIOD) + val upperBound = today.plusDays(GRACE_PERIOD) return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1) } - internal fun calculateInterval(episodes: List, zonedDateTime: ZonedDateTime): Int { - val sortedEpisodes = episodes - .sortedWith( - compareByDescending { it.dateUpload }.thenByDescending { it.dateFetch }, - ) - .take(50) - - val uploadDates = sortedEpisodes + internal fun calculateInterval(episodes: List, zone: ZoneId): Int { + val uploadDates = episodes.asSequence() .filter { it.dateUpload > 0L } + .sortedByDescending { it.dateUpload } .map { - ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone) .toLocalDate() .atStartOfDay() } .distinct() - val fetchDates = sortedEpisodes + .take(10) + .toList() + + val fetchDates = episodes.asSequence() + .sortedByDescending { it.dateFetch } .map { - ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone) .toLocalDate() .atStartOfDay() } .distinct() + .take(10) + .toList() val interval = when { // Enough upload date from source @@ -88,7 +86,7 @@ class SetAnimeFetchInterval( else -> 7 } - return interval.coerceIn(1, MAX_FETCH_INTERVAL) + return interval.coerceIn(1, MAX_INTERVAL) } private fun calculateNextUpdate( @@ -119,7 +117,7 @@ class SetAnimeFetchInterval( } private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int { - if (delta >= MAX_FETCH_INTERVAL) return MAX_FETCH_INTERVAL + if (delta >= MAX_INTERVAL) return MAX_INTERVAL // double delta again if missed more than 9 check in new delta val cycle = timeSinceLatest.floorDiv(delta) + 1 @@ -129,4 +127,10 @@ class SetAnimeFetchInterval( delta } } + + companion object { + const val MAX_INTERVAL = 28 + + private const val GRACE_PERIOD = 1L + } } diff --git a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/SetMangaFetchInterval.kt b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt similarity index 83% rename from domain/src/main/java/tachiyomi/domain/entries/manga/interactor/SetMangaFetchInterval.kt rename to domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt index 0b0dda0d5..d4044fef0 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/SetMangaFetchInterval.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt @@ -4,16 +4,13 @@ import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.MangaUpdate import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.items.chapter.model.Chapter -import uy.kohesive.injekt.api.get import java.time.Instant +import java.time.ZoneId import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import kotlin.math.absoluteValue -const val MAX_FETCH_INTERVAL = 28 -private const val FETCH_INTERVAL_GRACE_PERIOD = 1 - -class SetMangaFetchInterval( +class MangaFetchInterval( private val getChapterByMangaId: GetChapterByMangaId, ) { @@ -30,7 +27,7 @@ class SetMangaFetchInterval( val chapters = getChapterByMangaId.await(manga.id) val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval( chapters, - dateTime, + dateTime.zone, ) val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow) @@ -43,33 +40,34 @@ class SetMangaFetchInterval( fun getWindow(dateTime: ZonedDateTime): Pair { val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone) - val lowerBound = today.minusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong()) - val upperBound = today.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong()) + val lowerBound = today.minusDays(GRACE_PERIOD) + val upperBound = today.plusDays(GRACE_PERIOD) return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1) } - internal fun calculateInterval(chapters: List, zonedDateTime: ZonedDateTime): Int { - val sortedChapters = chapters - .sortedWith( - compareByDescending { it.dateUpload }.thenByDescending { it.dateFetch }, - ) - .take(50) - - val uploadDates = sortedChapters + internal fun calculateInterval(chapters: List, zone: ZoneId): Int { + val uploadDates = chapters.asSequence() .filter { it.dateUpload > 0L } + .sortedByDescending { it.dateUpload } .map { - ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone) .toLocalDate() .atStartOfDay() } .distinct() - val fetchDates = sortedChapters + .take(10) + .toList() + + val fetchDates = chapters.asSequence() + .sortedByDescending { it.dateFetch } .map { - ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone) + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone) .toLocalDate() .atStartOfDay() } .distinct() + .take(10) + .toList() val interval = when { // Enough upload date from source @@ -88,7 +86,7 @@ class SetMangaFetchInterval( else -> 7 } - return interval.coerceIn(1, MAX_FETCH_INTERVAL) + return interval.coerceIn(1, MAX_INTERVAL) } private fun calculateNextUpdate( @@ -119,7 +117,7 @@ class SetMangaFetchInterval( } private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int { - if (delta >= MAX_FETCH_INTERVAL) return MAX_FETCH_INTERVAL + if (delta >= MAX_INTERVAL) return MAX_INTERVAL // double delta again if missed more than 9 check in new delta val cycle = timeSinceLatest.floorDiv(delta) + 1 @@ -129,4 +127,10 @@ class SetMangaFetchInterval( delta } } + + companion object { + const val MAX_INTERVAL = 28 + + private const val GRACE_PERIOD = 1L + } } diff --git a/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt b/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt index f4dc0d06d..51c997bba 100644 --- a/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt +++ b/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt @@ -14,20 +14,16 @@ class StubAnimeSource( private val isInvalid: Boolean = name.isBlank() || lang.isBlank() - override suspend fun getAnimeDetails(anime: SAnime): SAnime { + override suspend fun getAnimeDetails(anime: SAnime): SAnime = throw AnimeSourceNotInstalledException() - } - override suspend fun getEpisodeList(anime: SAnime): List { + override suspend fun getEpisodeList(anime: SAnime): List = throw AnimeSourceNotInstalledException() - } - override suspend fun getVideoList(episode: SEpisode): List