From fd1c6437a6fc80048d102b30789e15f0ab7556cf Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun, 29 Oct 2023 15:12:39 +0100 Subject: [PATCH] merge14 Last commit merged: https://github.com/tachiyomiorg/tachiyomi/commit/f344831d5877d1d0a7772b00f498ddfb6e86da04 --- app/build.gradle.kts | 3 +- .../domain/entries/manga/model/Manga.kt | 11 +- .../manga/MangaExtensionDetailsScreen.kt | 2 +- .../category/ChangeCategoryDialog.kt | 2 +- .../category/components/CategoryDialogs.kt | 4 +- .../components/ItemDownloadIndicator.kt | 2 +- .../presentation/entries/ItemsDialogs.kt | 2 +- .../entries/anime/EpisodeSettingsDialog.kt | 2 +- .../entries/manga/ChapterSettingsDialog.kt | 2 +- .../presentation/history/HistoryDialog.kt | 2 +- .../library/DeleteLibraryEntryDialog.kt | 2 +- .../screen/ClearAnimeDatabaseScreen.kt | 2 +- .../settings/screen/ClearDatabaseScreen.kt | 2 +- .../settings/screen/SettingsAdvancedScreen.kt | 2 +- .../settings/screen/SettingsBackupScreen.kt | 4 +- .../settings/screen/SettingsLibraryScreen.kt | 2 +- .../settings/screen/SettingsPlayerScreen.kt | 2 +- .../settings/screen/SettingsReaderScreen.kt | 10 +- .../settings/widget/BasePreferenceWidget.kt | 2 +- .../widget/EditTextPreferenceWidget.kt | 2 +- .../widget/MultiSelectListPreferenceWidget.kt | 2 +- .../settings/widget/TriStateListDialog.kt | 2 +- .../track/TrackInfoDialogSelector.kt | 6 +- .../presentation/updates/UpdatesDialog.kt | 2 +- .../tachiyomi/data/cache/ChapterCache.kt | 30 ++--- .../tachiyomi/data/cache/EpisodeCache.kt | 30 ++--- .../data/download/manga/MangaDownloader.kt | 7 +- .../data/track/DeletableAnimeTrackService.kt | 11 ++ .../data/track/DeletableMangaTrackService.kt | 11 ++ .../tachiyomi/data/track/anilist/Anilist.kt | 22 +++- .../data/track/anilist/AnilistApi.kt | 44 +++++++ .../tachiyomi/data/track/kitsu/Kitsu.kt | 12 +- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 33 +++++ .../data/track/mangaupdates/MangaUpdates.kt | 8 +- .../track/mangaupdates/MangaUpdatesApi.kt | 13 ++ .../data/track/myanimelist/MyAnimeList.kt | 12 +- .../data/track/myanimelist/MyAnimeListApi.kt | 28 +++++ .../data/track/shikimori/Shikimori.kt | 14 ++- .../data/track/shikimori/ShikimoriApi.kt | 89 ++++++++++---- .../anime/track/AnimeTrackInfoDialog.kt | 116 +++++++++++++++++- .../manga/track/MangaTrackInfoDialog.kt | 116 +++++++++++++++++- .../kanade/tachiyomi/ui/main/MainActivity.kt | 2 +- .../player/settings/dialogs/PlayerDialog.kt | 4 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 40 +++--- .../tachiyomi/ui/reader/ReaderPageDialog.kt | 102 +++++++++++++++ .../tachiyomi/ui/reader/ReaderPageSheet.kt | 62 ---------- .../tachiyomi/ui/reader/ReaderViewModel.kt | 34 +++-- app/src/main/res/layout/reader_activity.xml | 5 + app/src/main/res/layout/reader_page_sheet.xml | 53 -------- .../main/res/layout/reader_pager_settings.xml | 18 +-- .../core/metadata/comicinfo/ComicInfo.kt | 7 +- .../tachiyomi/network/NetworkPreferences.kt | 2 +- gradle/compose.versions.toml | 1 + i18n/src/main/res/values/strings.xml | 6 +- presentation-core/build.gradle.kts | 3 +- .../core/components/ActionButton.kt | 40 ++++++ .../core/components/material/Tabs.kt | 29 ++--- .../presentation/core/screens/EmptyScreen.kt | 30 +---- .../presentation/core/util/Modifier.kt | 12 +- 59 files changed, 792 insertions(+), 328 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableAnimeTrackService.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableMangaTrackService.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt delete mode 100644 app/src/main/res/layout/reader_page_sheet.xml create mode 100644 presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f76354466..a45d4c125 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -165,7 +165,8 @@ dependencies { implementation(compose.material.icons) implementation(compose.animation) implementation(compose.animation.graphics) - implementation(compose.ui.tooling) + debugImplementation(compose.ui.tooling) + implementation(compose.ui.tooling.preview) implementation(compose.ui.util) implementation(compose.accompanist.webview) implementation(compose.accompanist.permissions) diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt index efb20696d..786277c69 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt @@ -95,10 +95,16 @@ fun Manga.hasCustomCover(coverCache: MangaCoverCache = Injekt.get()): Boolean { /** * Creates a ComicInfo instance based on the manga and chapter metadata. */ -fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo( +fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories: List?) = ComicInfo( title = ComicInfo.Title(chapter.name), series = ComicInfo.Series(manga.title), - number = chapter.chapterNumber.takeIf { it >= 0 }?.let { ComicInfo.Number(it.toString()) }, + number = chapter.chapterNumber.takeIf { it >= 0 }?.let { + if ((it.rem(1) == 0.0F)) { + ComicInfo.Number(it.toInt().toString()) + } else { + ComicInfo.Number(it.toString()) + } + }, web = ComicInfo.Web(chapterUrl), summary = manga.description?.let { ComicInfo.Summary(it) }, writer = manga.author?.let { ComicInfo.Writer(it) }, @@ -108,6 +114,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo publishingStatus = ComicInfo.PublishingStatusTachiyomi( ComicInfoPublishingStatus.toComicInfoValue(manga.status), ), + categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) }, inker = null, colorist = null, letterer = null, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt index f4f940d74..15467a96f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt @@ -415,7 +415,7 @@ fun NsfwWarningDialog( }, confirmButton = { TextButton(onClick = onClickConfirm) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, onDismissRequest = onClickConfirm, diff --git a/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt b/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt index 68f073f9e..01a0df115 100644 --- a/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt @@ -81,7 +81,7 @@ fun ChangeCategoryDialog( ) }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } } }, diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt index 3960a992e..c7c7c4faa 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt @@ -97,7 +97,7 @@ fun CategoryRenameDialog( onDismissRequest() }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { @@ -147,7 +147,7 @@ fun CategoryDeleteDialog( onDelete() onDismissRequest() },) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt index 5ec22b826..7c8f263fd 100644 --- a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt +++ b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt @@ -23,7 +23,7 @@ internal fun Modifier.commonClickable( ) = composed { val haptic = LocalHapticFeedback.current - this.combinedClickable( + Modifier.combinedClickable( enabled = enabled, onLongClick = { onLongClick() diff --git a/app/src/main/java/eu/kanade/presentation/entries/ItemsDialogs.kt b/app/src/main/java/eu/kanade/presentation/entries/ItemsDialogs.kt index c17c8c39a..07336dd45 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/ItemsDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/ItemsDialogs.kt @@ -28,7 +28,7 @@ fun DeleteItemsDialog( onConfirm() }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, title = { diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt index d350d9159..cd736a7e7 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt @@ -212,7 +212,7 @@ private fun SetAsDefaultDialog( onDismissRequest() }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt index 8143d8dd3..f9b93bbed 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt @@ -212,7 +212,7 @@ private fun SetAsDefaultDialog( onDismissRequest() }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt index cc65ce0f7..6590a5c53 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt @@ -94,7 +94,7 @@ fun HistoryDeleteAllDialog( onDelete() onDismissRequest() },) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/library/DeleteLibraryEntryDialog.kt b/app/src/main/java/eu/kanade/presentation/library/DeleteLibraryEntryDialog.kt index 5dbe97cc0..57f006564 100644 --- a/app/src/main/java/eu/kanade/presentation/library/DeleteLibraryEntryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/DeleteLibraryEntryDialog.kt @@ -56,7 +56,7 @@ fun DeleteLibraryEntryDialog( ) }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, title = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt index bdaa85ffa..1939e8e62 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt @@ -83,7 +83,7 @@ class ClearAnimeDatabaseScreen : Screen() { } }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt index 5ca86e8c1..3db394eae 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt @@ -83,7 +83,7 @@ class ClearDatabaseScreen : Screen() { } }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index ef12e6825..62ef22035 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -411,7 +411,7 @@ object SettingsAdvancedScreen : SearchableSettings { uriHandler.openUri("https://shizuku.rikka.app/download") }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt index cf1e5234c..f79cc6c38 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt @@ -213,7 +213,7 @@ object SettingsBackupScreen : SearchableSettings { onConfirm(flag) }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) @@ -266,7 +266,7 @@ object SettingsBackupScreen : SearchableSettings { }, confirmButton = { TextButton(onClick = onDismissRequest) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index 9de778e6e..57ba1aba7 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -516,7 +516,7 @@ object SettingsLibraryScreen : SearchableSettings { }, confirmButton = { TextButton(onClick = { onValueChanged(leadValue, followValue) }) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt index 5774b2c5b..9b3e22c37 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt @@ -379,7 +379,7 @@ object SettingsPlayerScreen : SearchableSettings { }, confirmButton = { TextButton(onClick = { onValueChanged(newLength) }) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 5c2402cc2..bd77db65d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -194,11 +194,6 @@ object SettingsReaderScreen : SearchableSettings { 6 to stringResource(R.string.scale_type_smart_fit), ), ), - Preference.PreferenceItem.SwitchPreference( - pref = readerPreferences.landscapeZoom(), - title = stringResource(R.string.pref_landscape_zoom), - enabled = imageScaleType == 1, - ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.zoomStart(), title = stringResource(R.string.pref_zoom_start), @@ -214,6 +209,11 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPreferences.cropBorders(), title = stringResource(R.string.pref_crop_borders), ), + Preference.PreferenceItem.SwitchPreference( + pref = readerPreferences.landscapeZoom(), + title = stringResource(R.string.pref_landscape_zoom), + enabled = imageScaleType == 1, + ), Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.navigateToPan(), title = stringResource(R.string.pref_navigate_pan), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt index 267c03e3e..aeb4f8570 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/BasePreferenceWidget.kt @@ -113,7 +113,7 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = comp tween(200) }, ) - then(Modifier.background(color = highlight)) + Modifier.background(color = highlight) } internal val TrailingWidgetBuffer = 16.dp diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt index d3dcd283f..b20ffacbb 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt @@ -84,7 +84,7 @@ fun EditTextPreferenceWidget( } }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt index f697ef4ad..b4659d53f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/MultiSelectListPreferenceWidget.kt @@ -96,7 +96,7 @@ fun MultiSelectListPreferenceWidget( isDialogShown = false }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt index 5c810f9e8..6983e206f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt @@ -137,7 +137,7 @@ fun TriStateListDialog( onValueChanged(included, excluded) }, ) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index 5137b934a..4b6d76a11 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -127,7 +127,7 @@ fun TrackScoreSelector( content = { WheelTextPicker( modifier = Modifier.align(Alignment.Center), - startIndex = selections.indexOf(selection).coerceAtLeast(0), + startIndex = selections.indexOf(selection).takeIf { it > 0 } ?: (selections.size / 2), items = selections, onSelectionChanged = { onSelectionChange(selections[it]) }, ) @@ -177,7 +177,7 @@ fun TrackDateSelector( Text(text = stringResource(android.R.string.cancel)) } TextButton(onClick = { onConfirm(pickerState.selectedDateMillis!!) }) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } } } @@ -215,7 +215,7 @@ fun BaseSelector( Text(text = stringResource(android.R.string.cancel)) } TextButton(onClick = onConfirm) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } } }, diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt index f5b180f21..4e02b1dfc 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt @@ -24,7 +24,7 @@ fun UpdatesDeleteConfirmationDialog( onConfirm() onDismissRequest() },) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, dismissButton = { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index cebffb3f7..594eaa81a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -28,25 +28,11 @@ import java.io.IOException */ class ChapterCache(private val context: Context) { - companion object { - /** Name of cache directory. */ - const val PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache" - - /** Application cache version. */ - const val PARAMETER_APP_VERSION = 1 - - /** The number of values per cache entry. Must be positive. */ - const val PARAMETER_VALUE_COUNT = 1 - - /** The maximum number of bytes this cache should use to store. */ - const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024 - } - private val json: Json by injectLazy() - /** Cache class used for cache management. */ + /** Cache class used for cache management. */ private val diskCache = DiskLruCache.open( - File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), + File(context.cacheDir, "chapter_disk_cache"), PARAMETER_APP_VERSION, PARAMETER_VALUE_COUNT, PARAMETER_CACHE_SIZE, @@ -55,8 +41,7 @@ class ChapterCache(private val context: Context) { /** * Returns directory of cache. */ - private val cacheDir: File - get() = diskCache.directory + private val cacheDir: File = diskCache.directory /** * Returns real size of directory. @@ -210,3 +195,12 @@ class ChapterCache(private val context: Context) { return "${chapter.mangaId}${chapter.url}" } } + +/** Application cache version. */ +private const val PARAMETER_APP_VERSION = 1 + +/** The number of values per cache entry. Must be positive. */ +private const val PARAMETER_VALUE_COUNT = 1 + +/** The maximum number of bytes this cache should use to store. */ +private const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt index 316116951..fc59c425a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/EpisodeCache.kt @@ -27,25 +27,11 @@ import java.io.IOException */ class EpisodeCache(private val context: Context) { - companion object { - /** Name of cache directory. */ - const val PARAMETER_CACHE_DIRECTORY = "episode_disk_cache" - - /** Application cache version. */ - const val PARAMETER_APP_VERSION = 1 - - /** The number of values per cache entry. Must be positive. */ - const val PARAMETER_VALUE_COUNT = 1 - - /** The maximum number of bytes this cache should use to store. */ - const val PARAMETER_CACHE_SIZE = 1000L * 1024 * 1024 - } - private val json: Json by injectLazy() - /** Cache class used for cache management. */ + /** Cache class used for cache management. */ private val diskCache = DiskLruCache.open( - File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), + File(context.cacheDir, "episode_disk_cache"), PARAMETER_APP_VERSION, PARAMETER_VALUE_COUNT, PARAMETER_CACHE_SIZE, @@ -54,8 +40,7 @@ class EpisodeCache(private val context: Context) { /** * Returns directory of cache. */ - val cacheDir: File - get() = diskCache.directory + val cacheDir: File = diskCache.directory /** * Returns real size of directory. @@ -193,3 +178,12 @@ class EpisodeCache(private val context: Context) { return "${episode.animeId}${episode.url}" } } + +/** Application cache version. */ +private const val PARAMETER_APP_VERSION = 1 + +/** The number of values per cache entry. Must be positive. */ +private const val PARAMETER_VALUE_COUNT = 1 + +/** The maximum number of bytes this cache should use to store. */ +private const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024 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 63265c224..c362ebb71 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 @@ -53,6 +53,7 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat +import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter @@ -82,6 +83,7 @@ class MangaDownloader( private val chapterCache: ChapterCache = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), private val xml: XML = Injekt.get(), + private val getCategories: GetMangaCategories = Injekt.get(), // SY --> private val sourcePreferences: SourcePreferences = Injekt.get(), // SY <-- @@ -634,14 +636,15 @@ class MangaDownloader( /** * Creates a ComicInfo.xml file inside the given directory. */ - private fun createComicInfoFile( + private suspend fun createComicInfoFile( dir: UniFile, manga: Manga, chapter: Chapter, source: HttpSource, ) { val chapterUrl = source.getChapterUrl(chapter.toSChapter()) - val comicInfo = getComicInfo(manga, chapter, chapterUrl) + val categories = getCategories.await(manga.id).map { it.name.trim() }.takeUnless { it.isEmpty() } + val comicInfo = getComicInfo(manga, chapter, chapterUrl, categories) // Remove the old file dir.findFile(COMIC_INFO_FILE)?.delete() dir.createFile(COMIC_INFO_FILE).openOutputStream().use { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableAnimeTrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableAnimeTrackService.kt new file mode 100644 index 000000000..3ea9c00c1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableAnimeTrackService.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.data.track + +import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack + +/** + * For track services api that support deleting a manga entry for a user's list + */ +interface DeletableAnimeTrackService { + + suspend fun delete(track: AnimeTrack): AnimeTrack +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableMangaTrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableMangaTrackService.kt new file mode 100644 index 000000000..4a68ce4d2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/DeletableMangaTrackService.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.data.track + +import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack + +/** + * For track services api that support deleting a manga entry for a user's list + */ +interface DeletableMangaTrackService { + + suspend fun delete(track: MangaTrack): MangaTrack +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 66e4ffc38..c06f53b37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableAnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch @@ -17,7 +19,7 @@ import uy.kohesive.injekt.injectLazy import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack -class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService { companion object { const val READING = 1 @@ -238,6 +240,24 @@ class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService return api.updateLibAnime(track) } + override suspend fun delete(track: MangaTrack): MangaTrack { + if (track.library_id == null || track.library_id!! == 0L) { + val libManga = api.findLibManga(track, getUsername().toInt()) ?: return track + track.library_id = libManga.library_id + } + + return api.deleteLibManga(track) + } + + override suspend fun delete(track: AnimeTrack): AnimeTrack { + if (track.library_id == null || track.library_id!! == 0L) { + val libManga = api.findLibAnime(track, getUsername().toInt()) ?: return track + track.library_id = libManga.library_id + } + + return api.deleteLibAnime(track) + } + override suspend fun bind(track: MangaTrack, hasReadChapters: Boolean): MangaTrack { val remoteTrack = api.findLibManga(track, getUsername().toInt()) return if (remoteTrack != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 8a740a7b6..59c80c82d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -112,6 +112,28 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } + suspend fun deleteLibManga(track: MangaTrack): MangaTrack { + return withIOContext { + val query = """ + |mutation DeleteManga(${'$'}listId: Int) { + |DeleteMediaListEntry(id: ${'$'}listId) { + |deleted + |} + |} + | + """.trimMargin() + val payload = buildJsonObject { + put("query", query) + putJsonObject("variables") { + put("listId", track.library_id) + } + } + authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + track + } + } + suspend fun addLibAnime(track: AnimeTrack): AnimeTrack { return withIOContext { val query = """ @@ -184,6 +206,28 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } + suspend fun deleteLibAnime(track: AnimeTrack): AnimeTrack { + return withIOContext { + val query = """ + |mutation DeleteAnime(${'$'}listId: Int) { + |DeleteMediaListEntry(id: ${'$'}listId) { + |deleted + |} + |} + | + """.trimMargin() + val payload = buildJsonObject { + put("query", query) + putJsonObject("variables") { + put("listId", track.library_id) + } + } + authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + track + } + } + suspend fun search(search: String): List { return withIOContext { val query = """ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index f1b395470..fbbc529d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableAnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch @@ -16,7 +18,7 @@ import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat -class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { +class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService, DeletableMangaTrackService, DeletableAnimeTrackService { companion object { const val READING = 1 @@ -136,6 +138,14 @@ class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { return api.updateLibAnime(track) } + override suspend fun delete(track: MangaTrack): MangaTrack { + return api.removeLibManga(track) + } + + override suspend fun delete(track: AnimeTrack): AnimeTrack { + return api.removeLibAnime(track) + } + override suspend fun bind(track: MangaTrack, hasReadChapters: Boolean): MangaTrack { val remoteTrack = api.findLibManga(track, getUserId()) return if (remoteTrack != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index b26ce36f6..6772c977b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch +import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess @@ -213,6 +214,38 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } + suspend fun removeLibManga(track: MangaTrack): MangaTrack { + return withIOContext { + authClient.newCall( + DELETE( + "${baseUrl}library-entries/${track.media_id}", + headers = headersOf( + "Content-Type", + "application/vnd.api+json", + ), + ), + ) + .awaitSuccess() + track + } + } + + suspend fun removeLibAnime(track: AnimeTrack): AnimeTrack { + return withIOContext { + authClient.newCall( + DELETE( + "${baseUrl}library-entries/${track.media_id}", + headers = headersOf( + "Content-Type", + "application/vnd.api+json", + ), + ), + ) + .awaitSuccess() + track + } + } + suspend fun search(query: String): List { return withIOContext { with(json) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index 0db8baf02..0b3746e0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -4,13 +4,14 @@ import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch -class MangaUpdates(id: Long) : TrackService(id), MangaTrackService { +class MangaUpdates(id: Long) : TrackService(id), MangaTrackService, DeletableMangaTrackService { companion object { const val READING_LIST = 0 @@ -67,6 +68,11 @@ class MangaUpdates(id: Long) : TrackService(id), MangaTrackService { return track } + override suspend fun delete(track: MangaTrack): MangaTrack { + api.deleteSeriesFromList(track) + return track + } + override suspend fun bind(track: MangaTrack, hasReadChapters: Boolean): MangaTrack { return try { val (series, rating) = api.getSeriesListItem(track) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt index 722bbcbff..14f1d0794 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt @@ -106,6 +106,19 @@ class MangaUpdatesApi( updateSeriesRating(track) } + suspend fun deleteSeriesFromList(track: MangaTrack) { + val body = buildJsonArray { + add(track.media_id) + } + authClient.newCall( + POST( + url = "$baseUrl/v1/lists/series/delete", + body = body.toString().toRequestBody(contentType), + ), + ) + .awaitSuccess() + } + private suspend fun getSeriesRating(track: MangaTrack): Rating? { return try { with(json) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 8aa555ab3..d148bcc71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableAnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch @@ -15,7 +17,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService { companion object { const val READING = 1 @@ -140,6 +142,14 @@ class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackSer return api.updateItem(track) } + override suspend fun delete(track: MangaTrack): MangaTrack { + return api.deleteMangaItem(track) + } + + override suspend fun delete(track: AnimeTrack): AnimeTrack { + return api.deleteAnimeItem(track) + } + override suspend fun bind(track: MangaTrack, hasReadChapters: Boolean): MangaTrack { val remoteTrack = api.findListItem(track) return if (remoteTrack != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 9bec442b4..372185e05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -249,6 +249,34 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI } } + suspend fun deleteMangaItem(track: MangaTrack): MangaTrack { + return withIOContext { + val request = Request.Builder() + .url(mangaUrl(track.media_id).toString()) + .delete() + .build() + with(json) { + authClient.newCall(request) + .awaitSuccess() + track + } + } + } + + suspend fun deleteAnimeItem(track: AnimeTrack): AnimeTrack { + return withIOContext { + val request = Request.Builder() + .url(animeUrl(track.media_id).toString()) + .delete() + .build() + with(json) { + authClient.newCall(request) + .awaitSuccess() + track + } + } + } + suspend fun findListItem(track: MangaTrack): MangaTrack? { return withIOContext { val uri = "$baseApiUrl/manga".toUri().buildUpon() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 5a91a5999..2a7f7bade 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableAnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch @@ -15,7 +17,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService, DeletableMangaTrackService, DeletableAnimeTrackService { companion object { const val READING = 1 @@ -87,6 +89,14 @@ class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackServi return api.updateLibAnime(track, getUsername()) } + override suspend fun delete(track: MangaTrack): MangaTrack { + return api.deleteLibManga(track) + } + + override suspend fun delete(track: AnimeTrack): AnimeTrack { + return api.deleteLibAnime(track) + } + override suspend fun bind(track: MangaTrack, hasReadChapters: Boolean): MangaTrack { val remoteTrack = api.findLibManga(track, getUsername()) return if (remoteTrack != null) { @@ -137,6 +147,7 @@ class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackServi override suspend fun refresh(track: MangaTrack): MangaTrack { api.findLibManga(track, getUsername())?.let { remoteTrack -> + track.library_id = remoteTrack.library_id track.copyPersonalFrom(remoteTrack) track.total_chapters = remoteTrack.total_chapters } @@ -145,6 +156,7 @@ class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackServi override suspend fun refresh(track: AnimeTrack): AnimeTrack { api.findLibAnime(track, getUsername())?.let { remoteTrack -> + track.library_id = remoteTrack.library_id track.copyPersonalFrom(remoteTrack) track.total_episodes = remoteTrack.total_episodes } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index f660a2b00..e33574039 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch +import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess @@ -37,20 +38,39 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter suspend fun addLibManga(track: MangaTrack, user_id: String): MangaTrack { return withIOContext { - val payload = buildJsonObject { - putJsonObject("user_rate") { - put("user_id", user_id) - put("target_id", track.media_id) - put("target_type", "Manga") - put("chapters", track.last_chapter_read.toInt()) - put("score", track.score.toInt()) - put("status", track.toShikimoriStatus()) + with(json) { + val payload = buildJsonObject { + putJsonObject("user_rate") { + put("user_id", user_id) + put("target_id", track.media_id) + put("target_type", "Manga") + put("chapters", track.last_chapter_read.toInt()) + put("score", track.score.toInt()) + put("status", track.toShikimoriStatus()) + } } + authClient.newCall( + POST( + "$apiUrl/v2/user_rates", + body = payload.toString().toRequestBody(jsonMime), + ), + ).awaitSuccess() + .parseAs() + .let { + track.library_id = it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request + } + track } + } + } + + suspend fun updateLibManga(track: MangaTrack, user_id: String): MangaTrack = addLibManga(track, user_id) + + suspend fun deleteLibManga(track: MangaTrack): MangaTrack { + return withIOContext { authClient.newCall( - POST( - "$apiUrl/v2/user_rates", - body = payload.toString().toRequestBody(jsonMime), + DELETE( + "$apiUrl/v2/user_rates/${track.library_id}", ), ).awaitSuccess() track @@ -59,30 +79,45 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter suspend fun addLibAnime(track: AnimeTrack, user_id: String): AnimeTrack { return withIOContext { - val payload = buildJsonObject { - putJsonObject("user_rate") { - put("user_id", user_id) - put("target_id", track.media_id) - put("target_type", "Manga") - put("chapters", track.last_episode_seen.toInt()) - put("score", track.score.toInt()) - put("status", track.toShikimoriStatus()) + with(json) { + val payload = buildJsonObject { + putJsonObject("user_rate") { + put("user_id", user_id) + put("target_id", track.media_id) + put("target_type", "Anime") + put("chapters", track.last_episode_seen.toInt()) + put("score", track.score.toInt()) + put("status", track.toShikimoriStatus()) + } } + authClient.newCall( + POST( + "$apiUrl/v2/user_rates", + body = payload.toString().toRequestBody(jsonMime), + ), + ).awaitSuccess() + .parseAs() + .let { + track.library_id = it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request + } + track } + } + } + + suspend fun updateLibAnime(track: AnimeTrack, user_id: String): AnimeTrack = addLibAnime(track, user_id) + + suspend fun deleteLibAnime(track: AnimeTrack): AnimeTrack { + return withIOContext { authClient.newCall( - POST( - "$apiUrl/v2/user_rates", - body = payload.toString().toRequestBody(jsonMime), + DELETE( + "$apiUrl/v2/user_rates/${track.library_id}", ), ).awaitSuccess() track } } - suspend fun updateLibManga(track: MangaTrack, user_id: String): MangaTrack = addLibManga(track, user_id) - - suspend fun updateLibAnime(track: AnimeTrack, user_id: String): AnimeTrack = addLibAnime(track, user_id) - suspend fun search(search: String): List { return withIOContext { val url = "$apiUrl/mangas".toUri().buildUpon() @@ -156,6 +191,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter title = mangas["name"]!!.jsonPrimitive.content media_id = obj["id"]!!.jsonPrimitive.long total_chapters = mangas["chapters"]!!.jsonPrimitive.int + library_id = obj["id"]!!.jsonPrimitive.long last_chapter_read = obj["chapters"]!!.jsonPrimitive.float score = (obj["score"]!!.jsonPrimitive.int).toFloat() status = toTrackStatus(obj["status"]!!.jsonPrimitive.content) @@ -168,6 +204,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter title = animes["name"]!!.jsonPrimitive.content media_id = obj["id"]!!.jsonPrimitive.long total_episodes = animes["episodes"]!!.jsonPrimitive.int + library_id = obj["id"]!!.jsonPrimitive.long last_episode_seen = obj["episodes"]!!.jsonPrimitive.float score = (obj["score"]!!.jsonPrimitive.int).toFloat() status = toTrackStatus(obj["status"]!!.jsonPrimitive.content) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt index cf5f3ac97..ac5c713b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.entries.anime.track import android.app.Application import android.content.Context import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth @@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Checkbox import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -49,6 +51,7 @@ import eu.kanade.presentation.track.anime.AnimeTrackServiceSearch import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.DeletableAnimeTrackService import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService @@ -158,7 +161,16 @@ data class AnimeTrackInfoDialogHomeScreen( } }, onOpenInBrowser = { openTrackerInBrowser(context, it) }, - ) { sm.unregisterTracking(it.service.id) } + onRemoved = { + navigator.push( + TrackAnimeServiceRemoveScreen( + animeId = animeId, + track = it.track!!, + serviceId = it.service.id, + ), + ) + }, + ) } /** @@ -175,7 +187,6 @@ data class AnimeTrackInfoDialogHomeScreen( private val animeId: Long, private val sourceId: Long, private val getTracks: GetAnimeTracks = Injekt.get(), - private val deleteTrack: DeleteAnimeTrack = Injekt.get(), ) : StateScreenModel(State()) { init { @@ -205,10 +216,6 @@ data class AnimeTrackInfoDialogHomeScreen( } } - fun unregisterTracking(serviceId: Long) { - coroutineScope.launchNonCancellable { deleteTrack.await(animeId, serviceId) } - } - private suspend fun refreshTrackers() { val insertAnimeTrack = Injekt.get() val getAnimeWithEpisodes = Injekt.get() @@ -730,3 +737,100 @@ data class TrackServiceSearchScreen( ) } } + +private data class TrackAnimeServiceRemoveScreen( + private val animeId: Long, + private val track: AnimeTrack, + private val serviceId: Long, +) : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val sm = rememberScreenModel { + Model( + animeId = animeId, + track = track, + service = Injekt.get().getService(serviceId)!!, + ) + } + val serviceName = stringResource(sm.getServiceNameRes()) + var removeRemoteTrack by remember { mutableStateOf(false) } + AlertDialogContent( + modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars), + icon = { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + ) + }, + title = { + Text( + text = stringResource(R.string.track_delete_title, serviceName), + textAlign = TextAlign.Center, + ) + }, + text = { + Column { + Text( + text = stringResource(R.string.track_delete_text, serviceName), + ) + if (sm.isServiceDeletable()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it }) + Text(text = stringResource(R.string.track_delete_remote_text, serviceName)) + } + } + } + }, + buttons = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy( + MaterialTheme.padding.small, + Alignment.End, + ), + ) { + TextButton(onClick = navigator::pop) { + Text(text = stringResource(R.string.action_cancel)) + } + FilledTonalButton( + onClick = { + sm.unregisterTracking(serviceId) + if (removeRemoteTrack) sm.deleteAnimeFromService() + navigator.pop() + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ), + ) { + Text(text = stringResource(R.string.action_ok)) + } + } + }, + ) + } + + private class Model( + private val animeId: Long, + private val track: AnimeTrack, + private val service: TrackService, + private val deleteTrack: DeleteAnimeTrack = Injekt.get(), + ) : ScreenModel { + + fun getServiceNameRes() = service.nameRes() + + fun isServiceDeletable() = service is DeletableAnimeTrackService + + fun deleteAnimeFromService() { + coroutineScope.launchNonCancellable { + (service as DeletableAnimeTrackService).delete(track.toDbTrack()) + } + } + + fun unregisterTracking(serviceId: Long) { + coroutineScope.launchNonCancellable { deleteTrack.await(animeId, serviceId) } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt index f8d408947..218526abd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.entries.manga.track import android.app.Application import android.content.Context import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth @@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Checkbox import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -48,6 +50,7 @@ import eu.kanade.presentation.track.manga.MangaTrackInfoDialogHome import eu.kanade.presentation.track.manga.MangaTrackServiceSearch import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.track.DeletableMangaTrackService import eu.kanade.tachiyomi.data.track.EnhancedMangaTrackService import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackManager @@ -158,7 +161,16 @@ data class MangaTrackInfoDialogHomeScreen( } }, onOpenInBrowser = { openTrackerInBrowser(context, it) }, - ) { sm.unregisterTracking(it.service.id) } + onRemoved = { + navigator.push( + TrackMangaServiceRemoveScreen( + mangaId = mangaId, + track = it.track!!, + serviceId = it.service.id, + ), + ) + }, + ) } /** @@ -175,7 +187,6 @@ data class MangaTrackInfoDialogHomeScreen( private val mangaId: Long, private val sourceId: Long, private val getTracks: GetMangaTracks = Injekt.get(), - private val deleteTrack: DeleteMangaTrack = Injekt.get(), ) : StateScreenModel(State()) { init { @@ -205,10 +216,6 @@ data class MangaTrackInfoDialogHomeScreen( } } - fun unregisterTracking(serviceId: Long) { - coroutineScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) } - } - private suspend fun refreshTrackers() { val insertTrack = Injekt.get() val getMangaWithChapters = Injekt.get() @@ -729,3 +736,100 @@ data class TrackServiceSearchScreen( ) } } + +private data class TrackMangaServiceRemoveScreen( + private val mangaId: Long, + private val track: MangaTrack, + private val serviceId: Long, +) : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val sm = rememberScreenModel { + Model( + mangaId = mangaId, + track = track, + service = Injekt.get().getService(serviceId)!!, + ) + } + val serviceName = stringResource(sm.getServiceNameRes()) + var removeRemoteTrack by remember { mutableStateOf(false) } + AlertDialogContent( + modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars), + icon = { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + ) + }, + title = { + Text( + text = stringResource(R.string.track_delete_title, serviceName), + textAlign = TextAlign.Center, + ) + }, + text = { + Column { + Text( + text = stringResource(R.string.track_delete_text, serviceName), + ) + if (sm.isServiceDeletable()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it }) + Text(text = stringResource(R.string.track_delete_remote_text, serviceName)) + } + } + } + }, + buttons = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy( + MaterialTheme.padding.small, + Alignment.End, + ), + ) { + TextButton(onClick = navigator::pop) { + Text(text = stringResource(R.string.action_cancel)) + } + FilledTonalButton( + onClick = { + sm.unregisterTracking(serviceId) + if (removeRemoteTrack) sm.deleteMangaFromService() + navigator.pop() + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ), + ) { + Text(text = stringResource(R.string.action_ok)) + } + } + }, + ) + } + + private class Model( + private val mangaId: Long, + private val track: MangaTrack, + private val service: TrackService, + private val deleteTrack: DeleteMangaTrack = Injekt.get(), + ) : ScreenModel { + + fun getServiceNameRes() = service.nameRes() + + fun isServiceDeletable() = service is DeletableMangaTrackService + + fun deleteMangaFromService() { + coroutineScope.launchNonCancellable { + (service as DeletableMangaTrackService).delete(track.toDbTrack()) + } + } + + fun unregisterTracking(serviceId: Long) { + coroutineScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index e9566a2c9..ac17a5d09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -273,7 +273,7 @@ class MainActivity : BaseActivity() { }, confirmButton = { TextButton(onClick = { showChangelog = false }) { - Text(text = stringResource(android.R.string.ok)) + Text(text = stringResource(R.string.action_ok)) } }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/PlayerDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/PlayerDialog.kt index a59e7449a..a6f4d3a05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/PlayerDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/PlayerDialog.kt @@ -20,7 +20,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.material.TextButton -// TODO: (Merge_Change) stringResource "android.R.string.ok" to be replaced with +// TODO: (Merge_Change) stringResource "R.string.action_ok" to be replaced with // "R.string.action_ok" @Composable @@ -71,7 +71,7 @@ fun PlayerDialog( } TextButton(onClick = onConfirm) { - Text(stringResource(android.R.string.ok)) + Text(stringResource(R.string.action_ok)) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 1443b5c8e..d0a1e0fd6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -411,6 +411,20 @@ class ReaderActivity : BaseActivity() { ) } + binding.dialogRoot.setComposeContent { + val state by viewModel.state.collectAsState() + + when (state.dialog) { + is ReaderViewModel.Dialog.Page -> ReaderPageDialog( + onDismissRequest = viewModel::closeDialog, + onSetAsCover = viewModel::setAsCover, + onShare = viewModel::shareImage, + onSave = viewModel::saveImage, + ) + null -> {} + } + } + // Init listeners on bottom menu binding.readerNav.setComposeContent { val state by viewModel.state.collectAsState() @@ -790,7 +804,7 @@ class ReaderActivity : BaseActivity() { * actions to perform is shown. */ fun onPageLongTap(page: ReaderPage) { - ReaderPageSheet(this, page).show() + viewModel.openPageDialog(page) } /** @@ -827,14 +841,6 @@ class ReaderActivity : BaseActivity() { } } - /** - * Called from the page sheet. It delegates the call to the presenter to do some IO, which - * will call [onShareImageResult] with the path the image was saved on when it's ready. - */ - fun shareImage(page: ReaderPage) { - viewModel.shareImage(page) - } - /** * Called from the presenter when a page is ready to be shared. It shows Android's default * sharing tool. @@ -850,14 +856,6 @@ class ReaderActivity : BaseActivity() { startActivity(Intent.createChooser(intent, getString(R.string.action_share))) } - /** - * Called from the page sheet. It delegates saving the image of the given [page] on external - * storage to the presenter. - */ - fun saveImage(page: ReaderPage) { - viewModel.saveImage(page) - } - /** * Called from the presenter when a page is saved or fails. It shows a message or logs the * event depending on the [result]. @@ -873,14 +871,6 @@ class ReaderActivity : BaseActivity() { } } - /** - * Called from the page sheet. It delegates setting the image of the given [page] as the - * cover to the presenter. - */ - fun setAsCover(page: ReaderPage) { - viewModel.setAsCover(page) - } - /** * Called from the presenter when a page is set as cover or fails. It shows a different message * depending on the [result]. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt new file mode 100644 index 000000000..b3b07be2a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageDialog.kt @@ -0,0 +1,102 @@ +package eu.kanade.tachiyomi.ui.reader + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Photo +import androidx.compose.material.icons.outlined.Save +import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.ActionButton +import tachiyomi.presentation.core.components.material.padding + +@Composable +fun ReaderPageDialog( + onDismissRequest: () -> Unit, + onSetAsCover: () -> Unit, + onShare: () -> Unit, + onSave: () -> Unit, +) { + var showSetCoverDialog by remember { mutableStateOf(false) } + + AdaptiveSheet( + onDismissRequest = onDismissRequest, + ) { + Row( + modifier = Modifier.padding(vertical = 16.dp), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.set_as_cover), + icon = Icons.Outlined.Photo, + onClick = { showSetCoverDialog = true }, + ) + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.action_share), + icon = Icons.Outlined.Share, + onClick = { + onShare() + onDismissRequest() + }, + ) + ActionButton( + modifier = Modifier.weight(1f), + title = stringResource(R.string.action_save), + icon = Icons.Outlined.Save, + onClick = { + onSave() + onDismissRequest() + }, + ) + } + } + + if (showSetCoverDialog) { + SetCoverDialog( + onConfirm = { + onSetAsCover() + showSetCoverDialog = false + }, + onDismiss = { showSetCoverDialog = false }, + ) + } +} + +@Composable +private fun SetCoverDialog( + onConfirm: () -> Unit, + onDismiss: () -> Unit, +) { + AlertDialog( + text = { + Text(stringResource(R.string.confirm_set_image_as_cover)) + }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(R.string.action_ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.action_cancel)) + } + }, + onDismissRequest = onDismiss, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt deleted file mode 100644 index 9dff32a8e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader - -import android.view.LayoutInflater -import android.view.View -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog - -/** - * Sheet to show when a page is long clicked. - */ -class ReaderPageSheet( - private val activity: ReaderActivity, - private val page: ReaderPage, -) : BaseBottomSheetDialog(activity) { - - private lateinit var binding: ReaderPageSheetBinding - - override fun createView(inflater: LayoutInflater): View { - binding = ReaderPageSheetBinding.inflate(activity.layoutInflater, null, false) - - binding.setAsCover.setOnClickListener { setAsCover() } - binding.share.setOnClickListener { share() } - binding.save.setOnClickListener { save() } - - return binding.root - } - - /** - * Sets the image of this page as the cover of the manga. - */ - private fun setAsCover() { - if (page.status != Page.State.READY) return - - MaterialAlertDialogBuilder(activity) - .setMessage(R.string.confirm_set_image_as_cover) - .setPositiveButton(android.R.string.ok) { _, _ -> - activity.setAsCover(page) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - - /** - * Shares the image of this page with external apps. - */ - private fun share() { - activity.shareImage(page) - dismiss() - } - - /** - * Saves the image of this page on external storage. - */ - private fun save() { - activity.saveImage(page) - dismiss() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index ce7dd1c1f..6f7823bb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -718,12 +718,21 @@ class ReaderViewModel( ) + filenameSuffix } + fun openPageDialog(page: ReaderPage) { + mutableState.update { it.copy(dialog = Dialog.Page(page)) } + } + + fun closeDialog() { + mutableState.update { it.copy(dialog = null) } + } + /** - * Saves the image of this [page] on the pictures directory and notifies the UI of the result. + * Saves the image of this the selected page on the pictures directory and notifies the UI of the result. * There's also a notification to allow sharing the image somewhere else or deleting it. */ - fun saveImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun saveImage() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val context = Injekt.get() @@ -757,14 +766,15 @@ class ReaderViewModel( } /** - * Shares the image of this [page] and notifies the UI with the path of the file to share. + * Shares the image of this the selected page and notifies the UI with the path of the file to share. * The image must be first copied to the internal partition because there are many possible * formats it can come from, like a zipped chapter, in which case it's not possible to directly * get a path to the file and it has to be decompressed somewhere first. Only the last shared * image will be kept so it won't be taking lots of internal disk space. */ - fun shareImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun shareImage() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val context = Injekt.get() @@ -790,10 +800,11 @@ class ReaderViewModel( } /** - * Sets the image of this [page] as cover and notifies the UI of the result. + * Sets the image of this the selected page as cover and notifies the UI of the result. */ - fun setAsCover(page: ReaderPage) { - if (page.status != Page.State.READY) return + fun setAsCover() { + val page = (state.value.dialog as? Dialog.Page)?.page + if (page?.status != Page.State.READY) return val manga = manga ?: return val stream = page.stream ?: return @@ -906,11 +917,16 @@ class ReaderViewModel( * Viewer used to display the pages (pager, webtoon, ...). */ val viewer: Viewer? = null, + val dialog: Dialog? = null, ) { val totalPages: Int get() = viewerChapters?.currChapter?.pages?.size ?: -1 } + sealed class Dialog { + data class Page(val page: ReaderPage) : Dialog() + } + sealed class Event { object ReloadViewerChapters : Event() data class SetOrientation(val orientation: Int) : Event() diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index 650fbcd6c..977922694 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -138,4 +138,9 @@ android:layout_height="match_parent" android:visibility="gone" /> + + diff --git a/app/src/main/res/layout/reader_page_sheet.xml b/app/src/main/res/layout/reader_page_sheet.xml deleted file mode 100644 index ab581a65f..000000000 --- a/app/src/main/res/layout/reader_page_sheet.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/reader_pager_settings.xml b/app/src/main/res/layout/reader_pager_settings.xml index d96e4fee5..bd430ca2f 100644 --- a/app/src/main/res/layout/reader_pager_settings.xml +++ b/app/src/main/res/layout/reader_pager_settings.xml @@ -38,15 +38,6 @@ android:entries="@array/image_scale_type" app:title="@string/pref_image_scale_type" /> - - + + { - return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/111.0") + return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0") } } diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 1bb556acd..1d1fff7f1 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -10,6 +10,7 @@ foundation = { module = "androidx.compose.foundation:foundation" } animation = { module = "androidx.compose.animation:animation" } animation-graphics = { module = "androidx.compose.animation:animation-graphics" } ui-tooling = { module = "androidx.compose.ui:ui-tooling" } +ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } ui-util = { module = "androidx.compose.ui:ui-util" } material3-core = { module = "androidx.compose.material3:material3" } diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 97016c417..3818dd8c1 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -121,6 +121,7 @@ Pin Unpin Cancel + OK Cancel all Cancel all for this series Sort @@ -404,7 +405,7 @@ Original size Smart fit Pan wide images - Zoom landscape image + Automatically zoom into wide images Zoom start position Automatic Left @@ -739,6 +740,9 @@ Remove date? This will remove your previously selected start date from %s This will remove your previously selected finish date from %s + Remove %s tracking? + This will remove the tracking locally. + Also remove from %s A category with this name already exists! diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index f7ce6ba62..53025fdaa 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -30,7 +30,8 @@ dependencies { implementation(compose.material.icons) implementation(compose.animation) implementation(compose.animation.graphics) - implementation(compose.ui.tooling) + debugImplementation(compose.ui.tooling) + implementation(compose.ui.tooling.preview) implementation(compose.ui.util) } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt new file mode 100644 index 000000000..54cbd9cdd --- /dev/null +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ActionButton.kt @@ -0,0 +1,40 @@ +package tachiyomi.presentation.core.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp + +@Composable +fun ActionButton( + modifier: Modifier = Modifier, + title: String, + icon: ImageVector, + onClick: () -> Unit, +) { + TextButton( + modifier = modifier, + onClick = onClick, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = icon, + contentDescription = null, + ) + Text( + text = title, + textAlign = TextAlign.Center, + ) + } + } +} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt index 44617ea10..5b029ac63 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt @@ -30,20 +30,21 @@ import tachiyomi.presentation.core.components.Pill private fun Modifier.tabIndicatorOffset( currentTabPosition: TabPosition, currentPageOffsetFraction: Float, -) = composed { - val currentTabWidth by animateDpAsState( - targetValue = currentTabPosition.width, - animationSpec = spring(stiffness = Spring.StiffnessMediumLow), - ) - val offset by animateDpAsState( - targetValue = currentTabPosition.left + (currentTabWidth * currentPageOffsetFraction), - animationSpec = spring(stiffness = Spring.StiffnessMediumLow), - ) - fillMaxWidth() - .wrapContentSize(Alignment.BottomStart) - .offset { IntOffset(x = offset.roundToPx(), y = 0) } - .width(currentTabWidth) -} +) = fillMaxWidth() + .wrapContentSize(Alignment.BottomStart) + .composed { + val currentTabWidth by animateDpAsState( + targetValue = currentTabPosition.width, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + ) + val offset by animateDpAsState( + targetValue = currentTabPosition.left + (currentTabWidth * currentPageOffsetFraction), + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + ) + Modifier + .offset { IntOffset(x = offset.roundToPx(), y = 0) } + .width(currentTabWidth) + } @Composable fun TabIndicator( diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt index 632763239..c3a0cbe79 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt @@ -4,17 +4,13 @@ import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.paddingFromBaseline import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -24,6 +20,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach +import tachiyomi.presentation.core.components.ActionButton import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.secondaryItemAlpha import kotlin.random.Random @@ -96,31 +93,6 @@ fun EmptyScreen( } } -@Composable -private fun ActionButton( - modifier: Modifier = Modifier, - title: String, - icon: ImageVector, - onClick: () -> Unit, -) { - TextButton( - modifier = modifier, - onClick = onClick, - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon( - imageVector = icon, - contentDescription = null, - ) - Spacer(Modifier.height(4.dp)) - Text( - text = title, - textAlign = TextAlign.Center, - ) - } - } -} - private val ERROR_FACES = listOf( "(・o・;)", "Σ(ಠ_ಠ)", diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt index fb8e13996..795c6e211 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt @@ -25,13 +25,13 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalFocusManager import tachiyomi.presentation.core.components.material.SecondaryItemAlpha -fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed { - if (isSelected) { +fun Modifier.selectedBackground(isSelected: Boolean): Modifier = if (isSelected) { + composed { val alpha = if (isSystemInDarkTheme()) 0.16f else 0.22f - background(MaterialTheme.colorScheme.secondary.copy(alpha = alpha)) - } else { - this + Modifier.background(MaterialTheme.colorScheme.secondary.copy(alpha = alpha)) } +} else { + this } fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha) @@ -40,7 +40,7 @@ fun Modifier.clickableNoIndication( onLongClick: (() -> Unit)? = null, onClick: () -> Unit, ): Modifier = composed { - this.combinedClickable( + Modifier.combinedClickable( interactionSource = remember { MutableInteractionSource() }, indication = null, onLongClick = onLongClick,