From e171d1058835e701340381c1366ebbe9c0b122c9 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Fri, 3 Oct 2025 19:21:50 -0400 Subject: [PATCH] 90% converted to GQL --- data/src/commonMain/graphql/Category.graphql | 74 ++ .../{Chapters.graphql => Chapter.graphql} | 23 - data/src/commonMain/graphql/Download.graphql | 41 + data/src/commonMain/graphql/Extension.graphql | 32 + data/src/commonMain/graphql/Global.graphql | 14 + data/src/commonMain/graphql/Library.graphql | 5 + data/src/commonMain/graphql/Manga.graphql | 32 + .../{Settings.graphql => Setting.graphql} | 109 +- data/src/commonMain/graphql/Source.graphql | 205 +++ data/src/commonMain/graphql/Update.graphql | 31 + .../fragments/CategoryFragments.graphql | 12 + .../fragments/ChapterFragments.graphql | 29 + .../fragments/ExtensionFragments.graphql | 14 + .../graphql/fragments/MangaFragments.graphql | 79 ++ .../graphql/fragments/SourceFragments.graphql | 11 + data/src/commonMain/graphql/schema.graphqls | 1101 +++++++++++++++-- .../ca/gosyer/jui/data/DataComponent.kt | 72 ++ .../jui/data/backup/BackupRepositoryImpl.kt | 2 + .../data/category/CategoryRepositoryImpl.kt | 173 +++ .../jui/data/chapter/ChapterRepositoryImpl.kt | 14 +- .../data/download/DownloadRepositoryImpl.kt | 102 ++ .../data/extension/ExtensionRepositoryImpl.kt | 105 ++ .../jui/data/global/GlobalRepositoryImpl.kt | 47 + .../jui/data/library/LibraryRepositoryImpl.kt | 34 + .../jui/data/manga/MangaRepositoryImpl.kt | 188 +++ .../data/settings/SettingsRepositoryImpl.kt | 184 ++- .../jui/data/source/SourceRepositoryImpl.kt | 452 +++++++ .../jui/data/updates/UpdatesRepositoryImpl.kt | 63 + .../jui/domain/backup/model/RestoreStatus.kt | 2 + .../category/interactor/AddMangaToCategory.kt | 8 +- .../category/interactor/CreateCategory.kt | 6 +- .../category/interactor/DeleteCategory.kt | 8 +- .../category/interactor/GetCategories.kt | 6 +- .../category/interactor/GetMangaCategories.kt | 8 +- .../interactor/GetMangaListFromCategory.kt | 8 +- .../category/interactor/ModifyCategory.kt | 8 +- .../interactor/RemoveMangaFromCategory.kt | 8 +- .../category/interactor/ReorderCategory.kt | 18 +- .../category/interactor/UpdateCategoryMeta.kt | 6 +- .../category/service/CategoryRepository.kt | 58 + .../interactor/BatchChapterDownload.kt | 9 +- .../download/interactor/ClearDownloadQueue.kt | 6 +- .../interactor/QueueChapterDownload.kt | 46 +- .../interactor/ReorderChapterDownload.kt | 52 +- .../download/interactor/StartDownloading.kt | 6 +- .../interactor/StopChapterDownload.kt | 46 +- .../download/interactor/StopDownloading.kt | 6 +- .../download/service/DownloadRepository.kt | 35 + .../extension/interactor/GetExtensionList.kt | 6 +- .../extension/interactor/InstallExtension.kt | 6 +- .../interactor/InstallExtensionFile.kt | 8 +- .../interactor/UninstallExtension.kt | 6 +- .../extension/interactor/UpdateExtension.kt | 6 +- .../extension/service/ExtensionRepository.kt | 32 + .../domain/global/interactor/GetGlobalMeta.kt | 6 +- .../global/interactor/UpdateGlobalMeta.kt | 6 +- .../domain/global/service/GlobalRepository.kt | 19 + .../library/interactor/AddMangaToLibrary.kt | 8 +- .../interactor/RemoveMangaFromLibrary.kt | 8 +- .../library/service/LibraryRepository.kt | 19 + .../jui/domain/manga/interactor/GetManga.kt | 8 +- .../domain/manga/interactor/GetMangaFull.kt | 59 - .../domain/manga/interactor/RefreshManga.kt | 8 +- .../manga/interactor/RefreshMangaFull.kt | 54 - .../manga/interactor/UpdateMangaMeta.kt | 6 +- .../ca/gosyer/jui/domain/manga/model/Manga.kt | 3 +- .../domain/manga/service/MangaRepository.kt | 38 + .../jui/domain/settings/model/AuthMode.kt | 9 + .../jui/domain/settings/model/DatabaseType.kt | 8 + .../settings/model/DownloadConversion.kt | 7 + .../model/KoreaderSyncChecksumMethod.kt | 7 + .../model/KoreaderSyncConflictStrategy.kt | 9 + .../domain/settings/model/SetSettingsInput.kt | 35 +- .../jui/domain/settings/model/Settings.kt | 35 +- .../jui/domain/settings/model/SortOrder.kt | 11 + .../domain/source/interactor/GetFilterList.kt | 20 +- .../source/interactor/GetLatestManga.kt | 8 +- .../source/interactor/GetPopularManga.kt | 8 +- .../source/interactor/GetQuickSearchManga.kt | 80 -- .../source/interactor/GetSearchManga.kt | 29 +- .../domain/source/interactor/GetSourceList.kt | 6 +- .../source/interactor/GetSourceSettings.kt | 8 +- .../source/interactor/SetSourceFilter.kt | 125 -- .../source/interactor/SetSourceSetting.kt | 43 +- ...CheckBoxFilter.kt => CheckBoxFilterOld.kt} | 4 +- .../{GroupFilter.kt => GroupFilterOld.kt} | 8 +- .../{HeaderFilter.kt => HeaderFilterOld.kt} | 4 +- .../{SelectFilter.kt => SelectFilterOld.kt} | 4 +- ...paratorFilter.kt => SeparatorFilterOld.kt} | 4 +- .../{SortFilter.kt => SortFilterOld.kt} | 4 +- .../model/sourcefilters/SourceFilterChange.kt | 72 +- .../model/sourcefilters/SourceFilterData.kt | 2 +- .../{SourceFilter.kt => SourceFilterOld.kt} | 2 +- .../{TextFilter.kt => TextFilterOld.kt} | 4 +- ...TriStateFilter.kt => TriStateFilterOld.kt} | 4 +- ...Preference.kt => CheckBoxPreferenceOld.kt} | 4 +- ...Preference.kt => EditTextPreferenceOld.kt} | 4 +- ...ListPreference.kt => ListPreferenceOld.kt} | 4 +- ...nce.kt => MultiSelectListPreferenceOld.kt} | 4 +- .../sourcepreference/SourcePreference.kt | 70 +- ...chPreference.kt => SwitchPreferenceOld.kt} | 4 +- .../domain/source/service/SourceRepository.kt | 52 + .../source/service/SourceRepositoryOld.kt | 14 +- .../updates/interactor/GetRecentUpdates.kt | 6 +- .../updates/interactor/UpdateCategory.kt | 8 +- .../updates/interactor/UpdateLibrary.kt | 6 +- .../updates/service/UpdatesRepository.kt | 22 + .../ui/base/chapter/ChapterDownloadButtons.kt | 2 +- .../categories/CategoriesScreenViewModel.kt | 4 +- .../ui/downloads/DownloadsScreenViewModel.kt | 10 +- .../jui/ui/manga/MangaScreenViewModel.kt | 47 +- .../jui/ui/manga/components/ChapterItem.kt | 8 +- .../ui/manga/components/MangaScreenContent.kt | 4 +- .../ca/gosyer/jui/ui/reader/ReaderMenu.kt | 6 + .../jui/ui/settings/SettingsBackupScreen.kt | 4 +- .../jui/ui/settings/SettingsServerScreen.kt | 98 +- .../jui/ui/sources/browse/SourceScreen.kt | 37 + .../sources/browse/SourceScreenViewModel.kt | 9 +- .../browse/filter/SourceFiltersMenu.kt | 20 +- .../browse/filter/SourceFiltersViewModel.kt | 55 +- .../browse/filter/model/SourceFiltersView.kt | 130 +- .../globalsearch/GlobalSearchViewModel.kt | 2 +- .../settings/SourceSettingsScreenViewModel.kt | 3 +- .../components/SourceSettingsScreenContent.kt | 5 +- .../settings/model/SourceSettingsView.kt | 127 +- .../jui/ui/updates/UpdatesScreenViewModel.kt | 2 +- 126 files changed, 4145 insertions(+), 1090 deletions(-) create mode 100644 data/src/commonMain/graphql/Category.graphql rename data/src/commonMain/graphql/{Chapters.graphql => Chapter.graphql} (82%) create mode 100644 data/src/commonMain/graphql/Download.graphql create mode 100644 data/src/commonMain/graphql/Extension.graphql create mode 100644 data/src/commonMain/graphql/Global.graphql create mode 100644 data/src/commonMain/graphql/Library.graphql create mode 100644 data/src/commonMain/graphql/Manga.graphql rename data/src/commonMain/graphql/{Settings.graphql => Setting.graphql} (52%) create mode 100644 data/src/commonMain/graphql/Source.graphql create mode 100644 data/src/commonMain/graphql/Update.graphql create mode 100644 data/src/commonMain/graphql/fragments/CategoryFragments.graphql create mode 100644 data/src/commonMain/graphql/fragments/ChapterFragments.graphql create mode 100644 data/src/commonMain/graphql/fragments/ExtensionFragments.graphql create mode 100644 data/src/commonMain/graphql/fragments/MangaFragments.graphql create mode 100644 data/src/commonMain/graphql/fragments/SourceFragments.graphql create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/category/CategoryRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/download/DownloadRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/extension/ExtensionRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/global/GlobalRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/library/LibraryRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/manga/MangaRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/source/SourceRepositoryImpl.kt create mode 100644 data/src/commonMain/kotlin/ca/gosyer/jui/data/updates/UpdatesRepositoryImpl.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/service/ExtensionRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/service/LibraryRepository.kt delete mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt delete mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/AuthMode.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DatabaseType.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DownloadConversion.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncChecksumMethod.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncConflictStrategy.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SortOrder.kt delete mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt delete mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceFilter.kt rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{CheckBoxFilter.kt => CheckBoxFilterOld.kt} (91%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{GroupFilter.kt => GroupFilterOld.kt} (78%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{HeaderFilter.kt => HeaderFilterOld.kt} (92%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{SelectFilter.kt => SelectFilterOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{SeparatorFilter.kt => SeparatorFilterOld.kt} (91%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{SortFilter.kt => SortFilterOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{SourceFilter.kt => SourceFilterOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{TextFilter.kt => TextFilterOld.kt} (91%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/{TriStateFilter.kt => TriStateFilterOld.kt} (91%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/{CheckBoxPreference.kt => CheckBoxPreferenceOld.kt} (89%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/{EditTextPreference.kt => EditTextPreferenceOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/{ListPreference.kt => ListPreferenceOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/{MultiSelectListPreference.kt => MultiSelectListPreferenceOld.kt} (93%) rename domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/{SwitchPreference.kt => SwitchPreferenceOld.kt} (89%) create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt create mode 100644 domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/service/UpdatesRepository.kt diff --git a/data/src/commonMain/graphql/Category.graphql b/data/src/commonMain/graphql/Category.graphql new file mode 100644 index 00000000..cbb1e684 --- /dev/null +++ b/data/src/commonMain/graphql/Category.graphql @@ -0,0 +1,74 @@ + +query GetMangaCategories($id: Int!) { + manga(id: $id) { + categories { + nodes { + ...CategoryFragment + } + } + } +} + +mutation AddMangaToCategories($id: Int!, $addToCategories: [Int!]!) { + updateMangaCategories( + input: {id: $id, patch: {addToCategories: $addToCategories}} + ) { + clientMutationId + } +} + +mutation RemoveMangaFromCategories($id: Int!, $removeFromCategories: [Int!]!) { + updateMangaCategories( + input: {id: $id, patch: {removeFromCategories: $removeFromCategories}} + ) { + clientMutationId + } +} + +query GetCategories { + categories(orderBy: ORDER, orderByType: ASC) { + nodes { + ...CategoryFragment + } + } +} + +mutation CreateCategory($name: String!) { + createCategory(input: {name: $name}) { + clientMutationId + } +} + +mutation ModifyCategory($id: Int!, $name: String!) { + updateCategory(input: {id: $id, patch: {name: $name}}) { + clientMutationId + } +} + +mutation ReorderCategory($id: Int!, $position: Int!) { + updateCategoryOrder(input: {id: $id, position: $position}) { + clientMutationId + } +} + +mutation DeleteCategory($categoryId: Int!) { + deleteCategory(input: {categoryId: $categoryId}) { + clientMutationId + } +} + +query GetCategoryManga($categoryId: [Int!]!) { + mangas(orderBy: ID, condition: {categoryIds: $categoryId}) { + nodes { + ...MangaFragment + } + } +} + +mutation SetCategoryMeta($categoryId: Int!, $key: String!, $value: String!) { + setCategoryMeta( + input: {meta: {categoryId: $categoryId, key: $key, value: $value}} + ) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Chapters.graphql b/data/src/commonMain/graphql/Chapter.graphql similarity index 82% rename from data/src/commonMain/graphql/Chapters.graphql rename to data/src/commonMain/graphql/Chapter.graphql index 39e070f4..c7b47b14 100644 --- a/data/src/commonMain/graphql/Chapters.graphql +++ b/data/src/commonMain/graphql/Chapter.graphql @@ -1,26 +1,3 @@ -fragment ChapterFragment on ChapterType { - chapterNumber - fetchedAt - id - isBookmarked - isDownloaded - isRead - lastPageRead - lastReadAt - mangaId - name - pageCount - realUrl - scanlator - sourceOrder - uploadDate - url - meta { - key - value - } -} - query GetChapter($id: Int!) { chapter(id: $id) { ...ChapterFragment diff --git a/data/src/commonMain/graphql/Download.graphql b/data/src/commonMain/graphql/Download.graphql new file mode 100644 index 00000000..f8977580 --- /dev/null +++ b/data/src/commonMain/graphql/Download.graphql @@ -0,0 +1,41 @@ +mutation StartDownloader { + startDownloader(input: {}) { + clientMutationId + } +} + +mutation StopDownloader { + stopDownloader(input: {}) { + clientMutationId + } +} + +mutation ClearDownloader { + clearDownloader(input: {}) { + clientMutationId + } +} + +mutation EnqueueChapterDownload($id: Int!) { + enqueueChapterDownload(input: {id: $id}) { + clientMutationId + } +} + +mutation DequeueChapterDownload($id: Int!) { + dequeueChapterDownload(input: {id: $id}) { + clientMutationId + } +} + +mutation ReorderChapterDownload($chapterId: Int!, $to: Int!) { + reorderChapterDownload(input: {chapterId: $chapterId, to: $to}) { + clientMutationId + } +} + +mutation EnqueueChapterDownloads($ids: [Int!]!) { + enqueueChapterDownloads(input: {ids: $ids}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Extension.graphql b/data/src/commonMain/graphql/Extension.graphql new file mode 100644 index 00000000..d66e34b5 --- /dev/null +++ b/data/src/commonMain/graphql/Extension.graphql @@ -0,0 +1,32 @@ + +mutation FetchExtensions { + fetchExtensions(input: {}) { + extensions { + ...ExtensionFragment + } + } +} + +mutation InstallExtension($pkgName: String!) { + updateExtension(input: {id: $pkgName, patch: {install: true}}) { + clientMutationId + } +} + +mutation InstallExternalExtension($extensionFile: Upload!) { + installExternalExtension(input: {extensionFile: $extensionFile}) { + clientMutationId + } +} + +mutation UninstallExtension($pkgName: String!) { + updateExtension(input: {id: $pkgName, patch: {uninstall: true}}) { + clientMutationId + } +} + +mutation UpdateExtension($pkgName: String!) { + updateExtension(input: {id: $pkgName, patch: {update: true}}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Global.graphql b/data/src/commonMain/graphql/Global.graphql new file mode 100644 index 00000000..b966609c --- /dev/null +++ b/data/src/commonMain/graphql/Global.graphql @@ -0,0 +1,14 @@ +query GetGlobalMeta { + metas { + nodes { + key + value + } + } +} + +mutation SetGlobalMeta($key: String!, $value: String!) { + setGlobalMeta(input: {meta: {key: $key, value: $value}}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Library.graphql b/data/src/commonMain/graphql/Library.graphql new file mode 100644 index 00000000..e62efefd --- /dev/null +++ b/data/src/commonMain/graphql/Library.graphql @@ -0,0 +1,5 @@ +mutation SetMangaInLibrary($id: Int!, $inLibrary: Boolean!) { + updateManga(input: {id: $id, patch: {inLibrary: $inLibrary}}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Manga.graphql b/data/src/commonMain/graphql/Manga.graphql new file mode 100644 index 00000000..7c8d7656 --- /dev/null +++ b/data/src/commonMain/graphql/Manga.graphql @@ -0,0 +1,32 @@ +mutation RefreshManga($id: Int!) { + fetchManga(input: {id: $id}) { + manga { + ...MangaFragment + } + } +} + +query GetManga($id: Int!) { + manga(id: $id) { + ...MangaFragment + } +} + +query GetMangaLibrary($id: Int!) { + manga(id: $id) { + ...LibraryMangaFragment + } +} + +mutation SetMangaMeta($mangaId: Int!, $key: String!, $value: String!) { + setMangaMeta(input: {meta: {key: $key, mangaId: $mangaId, value: $value}}) { + clientMutationId + } +} + +query GetThumbnailUrl($id: Int!) { + manga(id: $id) { + thumbnailUrl + thumbnailUrlLastFetched + } +} diff --git a/data/src/commonMain/graphql/Settings.graphql b/data/src/commonMain/graphql/Setting.graphql similarity index 52% rename from data/src/commonMain/graphql/Settings.graphql rename to data/src/commonMain/graphql/Setting.graphql index bdcd32af..12202305 100644 --- a/data/src/commonMain/graphql/Settings.graphql +++ b/data/src/commonMain/graphql/Setting.graphql @@ -1,15 +1,25 @@ fragment SettingsTypeFragment on SettingsType { + authMode + authPassword + authUsername + autoDownloadIgnoreReUploads autoDownloadNewChapters autoDownloadNewChaptersLimit backupInterval backupPath backupTTL backupTime - basicAuthEnabled - basicAuthPassword - basicAuthUsername + databasePassword + databaseType + databaseUrl + databaseUsername debugLogsEnabled downloadAsCbz + downloadConversions { + compressionLevel + mimeType + target + } downloadsPath electronPath excludeCompleted @@ -17,17 +27,38 @@ fragment SettingsTypeFragment on SettingsType { excludeNotStarted excludeUnreadChapters extensionRepos + flareSolverrAsResponseFallback flareSolverrEnabled flareSolverrSessionName flareSolverrSessionTtl flareSolverrTimeout flareSolverrUrl globalUpdateInterval - gqlDebugLogsEnabled initialOpenInBrowserEnabled ip + jwtAudience + jwtRefreshExpiry + jwtTokenExpiry + koreaderSyncChecksumMethod + koreaderSyncDeviceId + koreaderSyncPercentageTolerance + koreaderSyncServerUrl + koreaderSyncStrategyBackward + koreaderSyncStrategyForward + koreaderSyncUserkey + koreaderSyncUsername localSourcePath + maxLogFileSize + maxLogFiles + maxLogFolderSize maxSourcesInParallel + opdsChapterSortOrder + opdsEnablePageReadProgress + opdsItemsPerPage + opdsMarkAsReadOnDownload + opdsShowOnlyDownloadedChapters + opdsShowOnlyUnreadChapters + opdsUseBinaryFileSizes port socksProxyEnabled socksProxyHost @@ -50,17 +81,23 @@ query AllSettings { } mutation SetSettings( + $authMode: AuthMode = null, + $authPassword: String = null, + $authUsername: String = null, + $autoDownloadIgnoreReUploads: Boolean = null, $autoDownloadNewChapters: Boolean = null, $autoDownloadNewChaptersLimit: Int = null, $backupInterval: Int = null, $backupPath: String = null, $backupTTL: Int = null, $backupTime: String = null, - $basicAuthEnabled: Boolean = null, - $basicAuthPassword: String = null, - $basicAuthUsername: String = null, + $databasePassword: String = null, + $databaseType: DatabaseType = null, + $databaseUrl: String = null, + $databaseUsername: String = null, $debugLogsEnabled: Boolean = null, $downloadAsCbz: Boolean = null, + $downloadConversions: [SettingsDownloadConversionTypeInput!] = null, $downloadsPath: String = null, $electronPath: String = null, $excludeCompleted: Boolean = null, @@ -68,17 +105,38 @@ mutation SetSettings( $excludeNotStarted: Boolean = null, $excludeUnreadChapters: Boolean = null, $extensionRepos: [String!] = null, + $flareSolverrAsResponseFallback: Boolean = null, $flareSolverrEnabled: Boolean = null, $flareSolverrSessionName: String = null, $flareSolverrSessionTtl: Int = null, $flareSolverrTimeout: Int = null, $flareSolverrUrl: String = null, $globalUpdateInterval: Float = null, - $gqlDebugLogsEnabled: Boolean = null, $initialOpenInBrowserEnabled: Boolean = null, $ip: String = null, + $jwtAudience: String = null, + $jwtRefreshExpiry: Duration = null, + $jwtTokenExpiry: Duration = null, + $koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod = null, + $koreaderSyncDeviceId: String = null, + $koreaderSyncPercentageTolerance: Float = null, + $koreaderSyncServerUrl: String = null, + $koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy = null, + $koreaderSyncStrategyForward: KoreaderSyncConflictStrategy = null, + $koreaderSyncUserkey: String = null, + $koreaderSyncUsername: String = null, $localSourcePath: String = null, + $maxLogFileSize: String = null, + $maxLogFiles: Int = null, + $maxLogFolderSize: String = null, $maxSourcesInParallel: Int = null, + $opdsChapterSortOrder: SortOrder = null, + $opdsEnablePageReadProgress: Boolean = null, + $opdsItemsPerPage: Int = null, + $opdsMarkAsReadOnDownload: Boolean = null, + $opdsShowOnlyDownloadedChapters: Boolean = null, + $opdsShowOnlyUnreadChapters: Boolean = null, + $opdsUseBinaryFileSizes: Boolean = null, $port: Int = null, $socksProxyEnabled: Boolean = null, $socksProxyHost: String = null, @@ -96,17 +154,23 @@ mutation SetSettings( setSettings( input: { settings: { + authMode: $authMode, + authPassword: $authPassword, + authUsername: $authUsername, + autoDownloadIgnoreReUploads: $autoDownloadIgnoreReUploads, autoDownloadNewChapters: $autoDownloadNewChapters, autoDownloadNewChaptersLimit: $autoDownloadNewChaptersLimit, backupInterval: $backupInterval, backupPath: $backupPath, backupTTL: $backupTTL, backupTime: $backupTime, - basicAuthEnabled: $basicAuthEnabled, - basicAuthPassword: $basicAuthPassword, - basicAuthUsername: $basicAuthUsername, + databasePassword: $databasePassword, + databaseType: $databaseType, + databaseUrl: $databaseUrl, + databaseUsername: $databaseUsername, debugLogsEnabled: $debugLogsEnabled, downloadAsCbz: $downloadAsCbz, + downloadConversions: $downloadConversions, downloadsPath: $downloadsPath, electronPath: $electronPath, excludeCompleted: $excludeCompleted, @@ -114,17 +178,38 @@ mutation SetSettings( excludeNotStarted: $excludeNotStarted, excludeUnreadChapters: $excludeUnreadChapters, extensionRepos: $extensionRepos, + flareSolverrAsResponseFallback: $flareSolverrAsResponseFallback, flareSolverrEnabled: $flareSolverrEnabled, flareSolverrSessionName: $flareSolverrSessionName, flareSolverrSessionTtl: $flareSolverrSessionTtl, flareSolverrTimeout: $flareSolverrTimeout, flareSolverrUrl: $flareSolverrUrl, globalUpdateInterval: $globalUpdateInterval, - gqlDebugLogsEnabled: $gqlDebugLogsEnabled, initialOpenInBrowserEnabled: $initialOpenInBrowserEnabled, ip: $ip, + jwtAudience: $jwtAudience, + jwtRefreshExpiry: $jwtRefreshExpiry, + jwtTokenExpiry: $jwtTokenExpiry, + koreaderSyncChecksumMethod: $koreaderSyncChecksumMethod, + koreaderSyncDeviceId: $koreaderSyncDeviceId, + koreaderSyncPercentageTolerance: $koreaderSyncPercentageTolerance, + koreaderSyncServerUrl: $koreaderSyncServerUrl, + koreaderSyncStrategyBackward: $koreaderSyncStrategyBackward, + koreaderSyncStrategyForward: $koreaderSyncStrategyForward, + koreaderSyncUserkey: $koreaderSyncUserkey, + koreaderSyncUsername: $koreaderSyncUsername, localSourcePath: $localSourcePath, + maxLogFileSize: $maxLogFileSize, + maxLogFiles: $maxLogFiles, + maxLogFolderSize: $maxLogFolderSize, maxSourcesInParallel: $maxSourcesInParallel, + opdsChapterSortOrder: $opdsChapterSortOrder, + opdsEnablePageReadProgress: $opdsEnablePageReadProgress, + opdsItemsPerPage: $opdsItemsPerPage, + opdsMarkAsReadOnDownload: $opdsMarkAsReadOnDownload, + opdsShowOnlyDownloadedChapters: $opdsShowOnlyDownloadedChapters, + opdsShowOnlyUnreadChapters: $opdsShowOnlyUnreadChapters, + opdsUseBinaryFileSizes: $opdsUseBinaryFileSizes, port: $port, socksProxyEnabled: $socksProxyEnabled, socksProxyHost: $socksProxyHost, diff --git a/data/src/commonMain/graphql/Source.graphql b/data/src/commonMain/graphql/Source.graphql new file mode 100644 index 00000000..f5dac40c --- /dev/null +++ b/data/src/commonMain/graphql/Source.graphql @@ -0,0 +1,205 @@ +query GetSourceList { + sources { + nodes { + ...SourceFragment + } + } +} + +query GetSource($id: LongString!) { + source(id: $id) { + ...SourceFragment + } +} + +mutation FetchPopularManga($source: LongString!, $page: Int!) { + fetchSourceManga(input: {page: $page, source: $source, type: POPULAR}) { + hasNextPage + mangas { + ...MangaFragment + } + } +} + +mutation FetchLatestManga($source: LongString!, $page: Int!) { + fetchSourceManga(input: {page: $page, source: $source, type: LATEST}) { + hasNextPage + mangas { + ...MangaFragment + } + } +} + +mutation FetchSearchManga($source: LongString!, $page: Int!, $query: String, $filters: [FilterChangeInput!]) { + fetchSourceManga( + input: {page: $page, source: $source, type: SEARCH, query: $query, filters: $filters} + ) { + hasNextPage + mangas { + ...MangaFragment + } + } +} + + +query GetSourceFilters($id: LongString!) { + source(id: $id) { + filters { + ... on CheckBoxFilter { + type: __typename + checkBoxFilterDefault: default + name + } + ... on HeaderFilter { + type: __typename + name + } + ... on SelectFilter { + type: __typename + selectFilterDefault: default + name + values + } + ... on TriStateFilter { + type: __typename + triStateFilterDefault: default + name + } + ... on TextFilter { + type: __typename + textFilterDefault: default + name + } + ... on SortFilter { + type: __typename + sortFilterDefault: default { + ascending + index + } + name + values + } + ... on SeparatorFilter { + type: __typename + name + } + ... on GroupFilter { + type: __typename + name + filters { + ... on CheckBoxFilter { + type: __typename + checkBoxFilterDefault: default + name + } + ... on HeaderFilter { + type: __typename + name + } + ... on SelectFilter { + type: __typename + selectFilterDefault: default + name + values + } + ... on TriStateFilter { + type: __typename + triStateFilterDefault: default + name + } + ... on TextFilter { + type: __typename + textFilterDefault: default + name + } + ... on SortFilter { + type: __typename + sortFilterDefault: default { + ascending + index + } + name + values + } + ... on SeparatorFilter { + type: __typename + name + } + } + } + } + } +} + + +query GetSourcePreferences($id: LongString!) { + source(id: $id) { + preferences { + ... on CheckBoxPreference { + type: __typename + checkBoxCheckBoxCurrentValue: currentValue + summary + checkBoxDefault: default + visible + enabled + key + checkBoxTitle: title + } + ... on EditTextPreference { + type: __typename + editTextPreferenceCurrentValue: currentValue + editTextPreferenceDefault: default + editTextPreferenceTitle: title + text + summary + key + visible + enabled + dialogTitle + dialogMessage + } + ... on SwitchPreference { + type: __typename + switchPreferenceCurrentValue: currentValue + summary + key + visible + enabled + switchPreferenceDefault: default + switchPreferenceTitle: title + } + ... on MultiSelectListPreference { + type: __typename + dialogMessage + dialogTitle + multiSelectListPreferenceTitle: title + summary + key + visible + enabled + entryValues + entries + multiSelectListPreferenceDefault: default + multiSelectListPreferenceCurrentValue: currentValue + } + ... on ListPreference { + type: __typename + listPreferenceCurrentValue: currentValue + listPreferenceDefault: default + listPreferenceTitle: title + summary + key + visible + enabled + entryValues + entries + } + } + } +} + +mutation SetSourceSetting($source: LongString!, $change: SourcePreferenceChangeInput!) { + updateSourcePreference(input: {change: $change, source: $source}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/Update.graphql b/data/src/commonMain/graphql/Update.graphql new file mode 100644 index 00000000..33bc3cd9 --- /dev/null +++ b/data/src/commonMain/graphql/Update.graphql @@ -0,0 +1,31 @@ +query GetChapterUpdates($first: Int!, $offset: Int!) { + chapters( + filter: {inLibrary: {equalTo:true}} + first: $first + offset: $offset + order: [ + { by: FETCHED_AT, byType: DESC }, + { by: SOURCE_ORDER, byType: DESC }, + ] + ) { + nodes { + ...ChapterWithMangaFragment + } + pageInfo { + hasNextPage + } + totalCount + } +} + +mutation UpdateLibrary { + updateLibraryManga(input: {}) { + clientMutationId + } +} + +mutation UpdateCategory($categories: [Int!]!) { + updateCategoryManga(input: {categories: $categories}) { + clientMutationId + } +} diff --git a/data/src/commonMain/graphql/fragments/CategoryFragments.graphql b/data/src/commonMain/graphql/fragments/CategoryFragments.graphql new file mode 100644 index 00000000..ead5188e --- /dev/null +++ b/data/src/commonMain/graphql/fragments/CategoryFragments.graphql @@ -0,0 +1,12 @@ +fragment CategoryFragment on CategoryType { + id + name + order + default + includeInDownload + includeInUpdate + meta { + key + value + } +} diff --git a/data/src/commonMain/graphql/fragments/ChapterFragments.graphql b/data/src/commonMain/graphql/fragments/ChapterFragments.graphql new file mode 100644 index 00000000..df127109 --- /dev/null +++ b/data/src/commonMain/graphql/fragments/ChapterFragments.graphql @@ -0,0 +1,29 @@ +fragment ChapterFragment on ChapterType { + chapterNumber + fetchedAt + id + isBookmarked + isDownloaded + isRead + lastPageRead + lastReadAt + mangaId + name + pageCount + realUrl + scanlator + sourceOrder + uploadDate + url + meta { + key + value + } +} + +fragment ChapterWithMangaFragment on ChapterType { + ...ChapterFragment + manga { + ...MangaFragment + } +} diff --git a/data/src/commonMain/graphql/fragments/ExtensionFragments.graphql b/data/src/commonMain/graphql/fragments/ExtensionFragments.graphql new file mode 100644 index 00000000..38eabc4f --- /dev/null +++ b/data/src/commonMain/graphql/fragments/ExtensionFragments.graphql @@ -0,0 +1,14 @@ +fragment ExtensionFragment on ExtensionType { + apkName + hasUpdate + iconUrl + isInstalled + isNsfw + isObsolete + lang + name + pkgName + repo + versionCode + versionName +} diff --git a/data/src/commonMain/graphql/fragments/MangaFragments.graphql b/data/src/commonMain/graphql/fragments/MangaFragments.graphql new file mode 100644 index 00000000..d4b3b797 --- /dev/null +++ b/data/src/commonMain/graphql/fragments/MangaFragments.graphql @@ -0,0 +1,79 @@ +fragment MangaFragment on MangaType { + title + artist + author + description + status + sourceId + id + url + thumbnailUrl + thumbnailUrlLastFetched + initialized + genre + inLibrary + inLibraryAt + updateStrategy + meta { + key + value + } + realUrl + lastFetchedAt + chaptersLastFetchedAt + age +} + +fragment LibraryMangaFragment on MangaType { + title + artist + author + description + status + sourceId + id + url + thumbnailUrl + thumbnailUrlLastFetched + initialized + genre + inLibrary + inLibraryAt + updateStrategy + meta { + key + value + } + realUrl + lastFetchedAt + chaptersLastFetchedAt + downloadCount + chapters { + totalCount + } + lastReadChapter { + lastReadAt + } + age + bookmarkCount + chaptersAge + unreadCount + highestNumberedChapter { + chapterNumber + } + firstUnreadChapter { + id + } + latestUploadedChapter { + uploadDate + } + latestReadChapter { + lastReadAt + } + latestFetchedChapter { + sourceOrder + } + source { + ...SourceFragment + } +} diff --git a/data/src/commonMain/graphql/fragments/SourceFragments.graphql b/data/src/commonMain/graphql/fragments/SourceFragments.graphql new file mode 100644 index 00000000..b836350c --- /dev/null +++ b/data/src/commonMain/graphql/fragments/SourceFragments.graphql @@ -0,0 +1,11 @@ +fragment SourceFragment on SourceType { + id + name + displayName + baseUrl + iconUrl + isConfigurable + isNsfw + lang + supportsLatest +} diff --git a/data/src/commonMain/graphql/schema.graphqls b/data/src/commonMain/graphql/schema.graphqls index 94723702..c15aeee9 100644 --- a/data/src/commonMain/graphql/schema.graphqls +++ b/data/src/commonMain/graphql/schema.graphqls @@ -9,17 +9,27 @@ type AboutServerPayload { name: String! - revision: String! + revision: String! @deprecated(reason: "The version includes the revision as the patch number") version: String! } type AboutWebUI { - channel: String! + channel: WebUIChannel! tag: String! } +enum AuthMode { + NONE + + BASIC_AUTH + + SIMPLE_LOGIN + + UI_LOGIN +} + enum BackupRestoreState { IDLE @@ -30,6 +40,10 @@ enum BackupRestoreState { RESTORING_CATEGORIES RESTORING_MANGA + + RESTORING_META + + RESTORING_SETTINGS } type BackupRestoreStatus { @@ -45,6 +59,11 @@ input BindTrackInput { mangaId: Int! + """ + This will only work if the tracker of the track record supports private tracking + """ + private: Boolean + remoteId: LongString! trackerId: Int! @@ -59,6 +78,10 @@ type BindTrackPayload { input BooleanFilterInput { distinctFrom: Boolean + distinctFromAll: [Boolean!] + + distinctFromAny: [Boolean!] + equalTo: Boolean greaterThan: Boolean @@ -77,6 +100,10 @@ input BooleanFilterInput { notEqualTo: Boolean + notEqualToAll: [Boolean!] + + notEqualToAny: [Boolean!] + notIn: [Boolean!] } @@ -112,6 +139,12 @@ input CategoryFilterInput { order: IntFilterInput } +enum CategoryJobStatus { + UPDATING + + SKIPPED +} + type CategoryMetaType implements MetaType { categoryId: Int! @@ -148,6 +181,12 @@ enum CategoryOrderBy { ORDER } +input CategoryOrderInput { + by: CategoryOrderBy! + + byType: SortOrder +} + type CategoryType { default: Boolean! @@ -166,6 +205,12 @@ type CategoryType { meta: [CategoryMetaType!]! } +type CategoryUpdateType { + category: CategoryType! + + status: CategoryJobStatus! +} + input ChapterConditionInput { chapterNumber: Float @@ -292,6 +337,12 @@ enum ChapterOrderBy { FETCHED_AT } +input ChapterOrderInput { + by: ChapterOrderBy! + + byType: SortOrder +} + type ChapterType { chapterNumber: Float! @@ -341,11 +392,13 @@ type CheckBoxPreference { default: Boolean! - key: String! + enabled: Boolean! + + key: String summary: String - title: String! + title: String visible: Boolean! } @@ -388,12 +441,28 @@ type ClearDownloaderPayload { downloadStatus: DownloadStatus! } +input ConnectKoSyncAccountInput { + clientMutationId: String + + password: String! + + username: String! +} + input CreateBackupInput { clientMutationId: String includeCategories: Boolean includeChapters: Boolean + + includeClientData: Boolean + + includeHistory: Boolean + + includeServerSettings: Boolean + + includeTracking: Boolean } type CreateBackupPayload { @@ -427,6 +496,12 @@ A location in a connection that can be used for resuming pagination. """ scalar Cursor +enum DatabaseType { + H2 + + POSTGRESQL +} + input DeleteCategoryInput { categoryId: Int! @@ -568,6 +643,10 @@ type DequeueChapterDownloadsPayload { input DoubleFilterInput { distinctFrom: Float + distinctFromAll: [Float!] + + distinctFromAny: [Float!] + equalTo: Float greaterThan: Float @@ -586,9 +665,20 @@ input DoubleFilterInput { notEqualTo: Float + notEqualToAll: [Float!] + + notEqualToAny: [Float!] + notIn: [Float!] } +input DownloadChangedInput { + """ + Sets a max number of updates that can be contained in a download update message.Everything above this limit will be omitted and the "downloadStatus" should be re-fetched via the corresponding query. Due to the graphql subscription execution strategy not supporting batching for data loaders, the data loaders run into the n+1 problem, which can cause the server to get unresponsive until the status update has been handled. This is an issue e.g. when mass en- or dequeuing downloads. + """ + maxUpdates: Int +} + type DownloadEdge implements Edge { cursor: Cursor! @@ -622,6 +712,8 @@ type DownloadStatus { } type DownloadType { + position: Int! + progress: Float! state: DownloadState! @@ -633,12 +725,57 @@ type DownloadType { manga: MangaType! } +type DownloadUpdate { + download: DownloadType! + + type: DownloadUpdateType! +} + +enum DownloadUpdateType { + QUEUED + + DEQUEUED + + PAUSED + + STOPPED + + PROGRESS + + FINISHED + + ERROR + + POSITION +} + +type DownloadUpdates { + """ + The current download queue at the time of sending initial message. Is null for all following messages + """ + initial: [DownloadType!] + + """ + Indicates whether updates have been omitted based on the "maxUpdates" subscription variable. In case updates have been omitted, the "downloadStatus" query should be re-fetched. + """ + omittedUpdates: Boolean! + + state: DownloaderState! + + updates: [DownloadUpdate!]! +} + enum DownloaderState { STARTED STOPPED } +""" +An ISO-8601 encoded duration string +""" +scalar Duration + interface Edge { """ A cursor for use in pagination. @@ -660,7 +797,9 @@ type EditTextPreference { dialogTitle: String - key: String! + enabled: Boolean! + + key: String summary: String @@ -777,6 +916,12 @@ enum ExtensionOrderBy { APK_NAME } +input ExtensionOrderInput { + by: ExtensionOrderBy! + + byType: SortOrder +} + type ExtensionType { apkName: String! @@ -809,6 +954,8 @@ input FetchChapterPagesInput { chapterId: Int! clientMutationId: String + + format: String } type FetchChapterPagesPayload { @@ -817,6 +964,8 @@ type FetchChapterPagesPayload { clientMutationId: String pages: [String!]! + + syncConflict: SyncConflictInfoType } input FetchChaptersInput { @@ -883,6 +1032,18 @@ enum FetchSourceMangaType { LATEST } +input FetchTrackInput { + clientMutationId: String + + recordId: Int! +} + +type FetchTrackPayload { + clientMutationId: String + + trackRecord: TrackRecordType! +} + union Filter = CheckBoxFilter|GroupFilter|HeaderFilter|SelectFilter|SeparatorFilter|SortFilter|TextFilter|TriStateFilter input FilterChangeInput { @@ -904,6 +1065,10 @@ input FilterChangeInput { input FloatFilterInput { distinctFrom: Float + distinctFromAll: [Float!] + + distinctFromAny: [Float!] + equalTo: Float greaterThan: Float @@ -922,6 +1087,10 @@ input FloatFilterInput { notEqualTo: Float + notEqualToAll: [Float!] + + notEqualToAny: [Float!] + notIn: [Float!] } @@ -980,6 +1149,10 @@ type InstallExternalExtensionPayload { input IntFilterInput { distinctFrom: Int + distinctFromAll: [Int!] + + distinctFromAny: [Int!] + equalTo: Int greaterThan: Int @@ -998,23 +1171,90 @@ input IntFilterInput { notEqualTo: Int + notEqualToAll: [Int!] + + notEqualToAny: [Int!] + notIn: [Int!] } +type KoSyncConnectPayload { + clientMutationId: String + + message: String + + settings: SettingsType! + + success: Boolean! + + username: String +} + +type KoSyncStatusPayload { + isLoggedIn: Boolean! + + username: String +} + +enum KoreaderSyncChecksumMethod { + BINARY + + FILENAME +} + +enum KoreaderSyncConflictStrategy { + PROMPT + + KEEP_LOCAL + + KEEP_REMOTE + + DISABLED +} + +enum KoreaderSyncLegacyStrategy { + PROMPT + + SILENT + + SEND + + RECEIVE + + DISABLED +} + type LastUpdateTimestampPayload { timestamp: LongString! } +type LibraryUpdateStatus { + categoryUpdates: [CategoryUpdateType!]! + + jobsInfo: UpdaterJobsInfoType! + + mangaUpdates: [MangaUpdateType!]! +} + +input LibraryUpdateStatusChangedInput { + """ + Sets a max number of updates that can be contained in a updater update message.Everything above this limit will be omitted and the "updateStatus" should be re-fetched via the corresponding query. Due to the graphql subscription execution strategy not supporting batching for data loaders, the data loaders run into the n+1 problem, which can cause the server to get unresponsive until the status update has been handled. This is an issue e.g. when starting an update. + """ + maxUpdates: Int +} + type ListPreference { currentValue: String default: String + enabled: Boolean! + entries: [String!]! entryValues: [String!]! - key: String! + key: String summary: String @@ -1023,6 +1263,22 @@ type ListPreference { visible: Boolean! } +input LoginInput { + clientMutationId: String + + password: String! + + username: String! +} + +type LoginPayload { + accessToken: String! + + clientMutationId: String + + refreshToken: String! +} + input LoginTrackerCredentialsInput { clientMutationId: String @@ -1057,6 +1313,18 @@ type LoginTrackerOAuthPayload { tracker: TrackerType! } +input LogoutKoSyncAccountInput { + clientMutationId: String +} + +type LogoutKoSyncAccountPayload { + clientMutationId: String + + settings: SettingsType! + + success: Boolean! +} + input LogoutTrackerInput { clientMutationId: String @@ -1074,6 +1342,10 @@ type LogoutTrackerPayload { input LongFilterInput { distinctFrom: LongString + distinctFromAll: [LongString!] + + distinctFromAny: [LongString!] + equalTo: LongString greaterThan: LongString @@ -1092,6 +1364,10 @@ input LongFilterInput { notEqualTo: LongString + notEqualToAll: [LongString!] + + notEqualToAny: [LongString!] + notIn: [LongString!] } @@ -1184,6 +1460,18 @@ input MangaFilterInput { url: StringFilterInput } +enum MangaJobStatus { + PENDING + + RUNNING + + COMPLETE + + FAILED + + SKIPPED +} + type MangaMetaType implements MetaType { key: String! @@ -1222,6 +1510,12 @@ enum MangaOrderBy { LAST_FETCHED_AT } +input MangaOrderInput { + by: MangaOrderBy! + + byType: SortOrder +} + enum MangaStatus { UNKNOWN @@ -1241,6 +1535,10 @@ enum MangaStatus { input MangaStatusFilterInput { distinctFrom: MangaStatus + distinctFromAll: [MangaStatus!] + + distinctFromAny: [MangaStatus!] + equalTo: MangaStatus greaterThan: MangaStatus @@ -1259,6 +1557,10 @@ input MangaStatusFilterInput { notEqualTo: MangaStatus + notEqualToAll: [MangaStatus!] + + notEqualToAny: [MangaStatus!] + notIn: [MangaStatus!] } @@ -1301,6 +1603,8 @@ type MangaType { age: LongString + bookmarkCount: Int! + categories: CategoryNodeList! chapters: ChapterNodeList! @@ -1309,6 +1613,12 @@ type MangaType { downloadCount: Int! + firstUnreadChapter: ChapterType + + hasDuplicateChapters: Boolean! + + highestNumberedChapter: ChapterType + lastReadChapter: ChapterType latestFetchedChapter: ChapterType @@ -1326,6 +1636,12 @@ type MangaType { unreadCount: Int! } +type MangaUpdateType { + manga: MangaType! + + status: MangaJobStatus! +} + input MetaConditionInput { key: String @@ -1356,6 +1672,12 @@ enum MetaOrderBy { VALUE } +input MetaOrderInput { + by: MetaOrderBy! + + byType: SortOrder +} + interface MetaType { key: String! @@ -1371,11 +1693,13 @@ type MultiSelectListPreference { dialogTitle: String + enabled: Boolean! + entries: [String!]! entryValues: [String!]! - key: String! + key: String summary: String @@ -1389,114 +1713,134 @@ type Mutation { restoreBackup(input: RestoreBackupInput!): RestoreBackupPayload! - createCategory(input: CreateCategoryInput!): CreateCategoryPayload! + createCategory(input: CreateCategoryInput!): CreateCategoryPayload - deleteCategory(input: DeleteCategoryInput!): DeleteCategoryPayload! + deleteCategory(input: DeleteCategoryInput!): DeleteCategoryPayload - deleteCategoryMeta(input: DeleteCategoryMetaInput!): DeleteCategoryMetaPayload! + deleteCategoryMeta(input: DeleteCategoryMetaInput!): DeleteCategoryMetaPayload - setCategoryMeta(input: SetCategoryMetaInput!): SetCategoryMetaPayload! + setCategoryMeta(input: SetCategoryMetaInput!): SetCategoryMetaPayload - updateCategories(input: UpdateCategoriesInput!): UpdateCategoriesPayload! + updateCategories(input: UpdateCategoriesInput!): UpdateCategoriesPayload - updateCategory(input: UpdateCategoryInput!): UpdateCategoryPayload! + updateCategory(input: UpdateCategoryInput!): UpdateCategoryPayload - updateCategoryOrder(input: UpdateCategoryOrderInput!): UpdateCategoryOrderPayload! + updateCategoryOrder(input: UpdateCategoryOrderInput!): UpdateCategoryOrderPayload - updateMangaCategories(input: UpdateMangaCategoriesInput!): UpdateMangaCategoriesPayload! + updateMangaCategories(input: UpdateMangaCategoriesInput!): UpdateMangaCategoriesPayload - updateMangasCategories(input: UpdateMangasCategoriesInput!): UpdateMangasCategoriesPayload! + updateMangasCategories(input: UpdateMangasCategoriesInput!): UpdateMangasCategoriesPayload - deleteChapterMeta(input: DeleteChapterMetaInput!): DeleteChapterMetaPayload! + deleteChapterMeta(input: DeleteChapterMetaInput!): DeleteChapterMetaPayload - fetchChapterPages(input: FetchChapterPagesInput!): FetchChapterPagesPayload! + fetchChapterPages(input: FetchChapterPagesInput!): FetchChapterPagesPayload - fetchChapters(input: FetchChaptersInput!): FetchChaptersPayload! + fetchChapters(input: FetchChaptersInput!): FetchChaptersPayload - setChapterMeta(input: SetChapterMetaInput!): SetChapterMetaPayload! + setChapterMeta(input: SetChapterMetaInput!): SetChapterMetaPayload - updateChapter(input: UpdateChapterInput!): UpdateChapterPayload! + updateChapter(input: UpdateChapterInput!): UpdateChapterPayload - updateChapters(input: UpdateChaptersInput!): UpdateChaptersPayload! + updateChapters(input: UpdateChaptersInput!): UpdateChaptersPayload - clearDownloader(input: ClearDownloaderInput!): ClearDownloaderPayload! + clearDownloader(input: ClearDownloaderInput!): ClearDownloaderPayload - deleteDownloadedChapter(input: DeleteDownloadedChapterInput!): DeleteDownloadedChapterPayload! + deleteDownloadedChapter(input: DeleteDownloadedChapterInput!): DeleteDownloadedChapterPayload - deleteDownloadedChapters(input: DeleteDownloadedChaptersInput!): DeleteDownloadedChaptersPayload! + deleteDownloadedChapters(input: DeleteDownloadedChaptersInput!): DeleteDownloadedChaptersPayload - dequeueChapterDownload(input: DequeueChapterDownloadInput!): DequeueChapterDownloadPayload! + dequeueChapterDownload(input: DequeueChapterDownloadInput!): DequeueChapterDownloadPayload - dequeueChapterDownloads(input: DequeueChapterDownloadsInput!): DequeueChapterDownloadsPayload! + dequeueChapterDownloads(input: DequeueChapterDownloadsInput!): DequeueChapterDownloadsPayload - enqueueChapterDownload(input: EnqueueChapterDownloadInput!): EnqueueChapterDownloadPayload! + enqueueChapterDownload(input: EnqueueChapterDownloadInput!): EnqueueChapterDownloadPayload - enqueueChapterDownloads(input: EnqueueChapterDownloadsInput!): EnqueueChapterDownloadsPayload! + enqueueChapterDownloads(input: EnqueueChapterDownloadsInput!): EnqueueChapterDownloadsPayload - reorderChapterDownload(input: ReorderChapterDownloadInput!): ReorderChapterDownloadPayload! + reorderChapterDownload(input: ReorderChapterDownloadInput!): ReorderChapterDownloadPayload - startDownloader(input: StartDownloaderInput!): StartDownloaderPayload! + startDownloader(input: StartDownloaderInput!): StartDownloaderPayload - stopDownloader(input: StopDownloaderInput!): StopDownloaderPayload! + stopDownloader(input: StopDownloaderInput!): StopDownloaderPayload - fetchExtensions(input: FetchExtensionsInput!): FetchExtensionsPayload! + fetchExtensions(input: FetchExtensionsInput!): FetchExtensionsPayload - installExternalExtension(input: InstallExternalExtensionInput!): InstallExternalExtensionPayload! + installExternalExtension(input: InstallExternalExtensionInput!): InstallExternalExtensionPayload - updateExtension(input: UpdateExtensionInput!): UpdateExtensionPayload! + updateExtension(input: UpdateExtensionInput!): UpdateExtensionPayload - updateExtensions(input: UpdateExtensionsInput!): UpdateExtensionsPayload! + updateExtensions(input: UpdateExtensionsInput!): UpdateExtensionsPayload clearCachedImages(input: ClearCachedImagesInput!): ClearCachedImagesPayload! - resetWebUIUpdateStatus: WebUIUpdateStatus! + resetWebUIUpdateStatus: WebUIUpdateStatus - updateWebUI(input: WebUIUpdateInput!): WebUIUpdatePayload! + updateWebUI(input: WebUIUpdateInput!): WebUIUpdatePayload - deleteMangaMeta(input: DeleteMangaMetaInput!): DeleteMangaMetaPayload! + connectKoSyncAccount(input: ConnectKoSyncAccountInput!): KoSyncConnectPayload! - fetchManga(input: FetchMangaInput!): FetchMangaPayload! + logoutKoSyncAccount(input: LogoutKoSyncAccountInput!): LogoutKoSyncAccountPayload! - setMangaMeta(input: SetMangaMetaInput!): SetMangaMetaPayload! + pullKoSyncProgress(input: PullKoSyncProgressInput!): PullKoSyncProgressPayload - updateManga(input: UpdateMangaInput!): UpdateMangaPayload! + pushKoSyncProgress(input: PushKoSyncProgressInput!): PushKoSyncProgressPayload - updateMangas(input: UpdateMangasInput!): UpdateMangasPayload! + deleteMangaMeta(input: DeleteMangaMetaInput!): DeleteMangaMetaPayload - deleteGlobalMeta(input: DeleteGlobalMetaInput!): DeleteGlobalMetaPayload! + fetchManga(input: FetchMangaInput!): FetchMangaPayload - setGlobalMeta(input: SetGlobalMetaInput!): SetGlobalMetaPayload! + setMangaMeta(input: SetMangaMetaInput!): SetMangaMetaPayload + + updateManga(input: UpdateMangaInput!): UpdateMangaPayload + + updateMangas(input: UpdateMangasInput!): UpdateMangasPayload + + deleteGlobalMeta(input: DeleteGlobalMetaInput!): DeleteGlobalMetaPayload + + setGlobalMeta(input: SetGlobalMetaInput!): SetGlobalMetaPayload resetSettings(input: ResetSettingsInput!): ResetSettingsPayload! setSettings(input: SetSettingsInput!): SetSettingsPayload! - deleteSourceMeta(input: DeleteSourceMetaInput!): DeleteSourceMetaPayload! + deleteSourceMeta(input: DeleteSourceMetaInput!): DeleteSourceMetaPayload - fetchSourceManga(input: FetchSourceMangaInput!): FetchSourceMangaPayload! + fetchSourceManga(input: FetchSourceMangaInput!): FetchSourceMangaPayload - setSourceMeta(input: SetSourceMetaInput!): SetSourceMetaPayload! + setSourceMeta(input: SetSourceMetaInput!): SetSourceMetaPayload - updateSourcePreference(input: UpdateSourcePreferenceInput!): UpdateSourcePreferencePayload! + updateSourcePreference(input: UpdateSourcePreferenceInput!): UpdateSourcePreferencePayload bindTrack(input: BindTrackInput!): BindTrackPayload! + fetchTrack(input: FetchTrackInput!): FetchTrackPayload! + loginTrackerCredentials(input: LoginTrackerCredentialsInput!): LoginTrackerCredentialsPayload! loginTrackerOAuth(input: LoginTrackerOAuthInput!): LoginTrackerOAuthPayload! logoutTracker(input: LogoutTrackerInput!): LogoutTrackerPayload! + trackProgress(input: TrackProgressInput!): TrackProgressPayload + + unbindTrack(input: UnbindTrackInput!): UnbindTrackPayload! + updateTrack(input: UpdateTrackInput!): UpdateTrackPayload! - updateCategoryManga(input: UpdateCategoryMangaInput!): UpdateCategoryMangaPayload! + updateCategoryManga(input: UpdateCategoryMangaInput!): UpdateCategoryMangaPayload - updateLibraryManga(input: UpdateLibraryMangaInput!): UpdateLibraryMangaPayload! + updateLibrary(input: UpdateLibraryInput!): UpdateLibraryPayload + + updateLibraryManga(input: UpdateLibraryMangaInput!): UpdateLibraryMangaPayload updateStop(input: UpdateStopInput!): UpdateStopPayload! + + login(input: LoginInput!): LoginPayload! + + refreshToken(input: RefreshTokenInput!): RefreshTokenPayload! } -union Node = CategoryMetaType|CategoryType|ChapterMetaType|ChapterType|DownloadType|ExtensionType|GlobalMetaType|MangaMetaType|MangaType|PartialSettingsType|SettingsType|SourceMetaType|SourceType|TrackRecordType|TrackerType +union Node = CategoryMetaType|CategoryType|ChapterMetaType|ChapterType|DownloadType|DownloadUpdate|ExtensionType|GlobalMetaType|MangaMetaType|MangaType|PartialSettingsType|SettingsType|SourceMetaType|SourceType|TrackRecordType|TrackerType interface NodeList { """ @@ -1543,8 +1887,16 @@ type PageInfo { } type PartialSettingsType implements Settings { + authMode: AuthMode + + authPassword: String + + authUsername: String + autoDownloadAheadLimit: Int @deprecated(reason: "Replaced with autoDownloadNewChaptersLimit, replace with autoDownloadNewChaptersLimit") + autoDownloadIgnoreReUploads: Boolean + autoDownloadNewChapters: Boolean autoDownloadNewChaptersLimit: Int @@ -1557,16 +1909,26 @@ type PartialSettingsType implements Settings { backupTime: String - basicAuthEnabled: Boolean + basicAuthEnabled: Boolean @deprecated(reason: "Removed - prefer authMode, replace with authMode") - basicAuthPassword: String + basicAuthPassword: String @deprecated(reason: "Removed - prefer authPassword, replace with authPassword") - basicAuthUsername: String + basicAuthUsername: String @deprecated(reason: "Removed - prefer authUsername, replace with authUsername") + + databasePassword: String + + databaseType: DatabaseType + + databaseUrl: String + + databaseUsername: String debugLogsEnabled: Boolean downloadAsCbz: Boolean + downloadConversions: [SettingsDownloadConversionType!] + downloadsPath: String electronPath: String @@ -1581,6 +1943,8 @@ type PartialSettingsType implements Settings { extensionRepos: [String!] + flareSolverrAsResponseFallback: Boolean + flareSolverrEnabled: Boolean flareSolverrSessionName: String @@ -1593,16 +1957,60 @@ type PartialSettingsType implements Settings { globalUpdateInterval: Float - gqlDebugLogsEnabled: Boolean + gqlDebugLogsEnabled: Boolean @deprecated(reason: "Removed - does not do anything") initialOpenInBrowserEnabled: Boolean ip: String + jwtAudience: String + + jwtRefreshExpiry: Duration + + jwtTokenExpiry: Duration + + koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod + + koreaderSyncDeviceId: String + + koreaderSyncPercentageTolerance: Float + + koreaderSyncServerUrl: String + + koreaderSyncStrategy: KoreaderSyncLegacyStrategy @deprecated(reason: "Replaced with koreaderSyncStrategyForward and koreaderSyncStrategyBackward, replace with koreaderSyncStrategyForward, koreaderSyncStrategyBackward") + + koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy + + koreaderSyncStrategyForward: KoreaderSyncConflictStrategy + + koreaderSyncUserkey: String + + koreaderSyncUsername: String + localSourcePath: String + maxLogFileSize: String + + maxLogFiles: Int + + maxLogFolderSize: String + maxSourcesInParallel: Int + opdsChapterSortOrder: SortOrder + + opdsEnablePageReadProgress: Boolean + + opdsItemsPerPage: Int + + opdsMarkAsReadOnDownload: Boolean + + opdsShowOnlyDownloadedChapters: Boolean + + opdsShowOnlyUnreadChapters: Boolean + + opdsUseBinaryFileSizes: Boolean + port: Int socksProxyEnabled: Boolean @@ -1631,8 +2039,16 @@ type PartialSettingsType implements Settings { } input PartialSettingsTypeInput { + authMode: AuthMode + + authPassword: String + + authUsername: String + autoDownloadAheadLimit: Int @deprecated(reason: "Replaced with autoDownloadNewChaptersLimit, replace with autoDownloadNewChaptersLimit") + autoDownloadIgnoreReUploads: Boolean + autoDownloadNewChapters: Boolean autoDownloadNewChaptersLimit: Int @@ -1645,16 +2061,26 @@ input PartialSettingsTypeInput { backupTime: String - basicAuthEnabled: Boolean + basicAuthEnabled: Boolean @deprecated(reason: "Removed - prefer authMode, replace with authMode") - basicAuthPassword: String + basicAuthPassword: String @deprecated(reason: "Removed - prefer authPassword, replace with authPassword") - basicAuthUsername: String + basicAuthUsername: String @deprecated(reason: "Removed - prefer authUsername, replace with authUsername") + + databasePassword: String + + databaseType: DatabaseType + + databaseUrl: String + + databaseUsername: String debugLogsEnabled: Boolean downloadAsCbz: Boolean + downloadConversions: [SettingsDownloadConversionTypeInput!] + downloadsPath: String electronPath: String @@ -1669,6 +2095,8 @@ input PartialSettingsTypeInput { extensionRepos: [String!] + flareSolverrAsResponseFallback: Boolean + flareSolverrEnabled: Boolean flareSolverrSessionName: String @@ -1681,16 +2109,60 @@ input PartialSettingsTypeInput { globalUpdateInterval: Float - gqlDebugLogsEnabled: Boolean + gqlDebugLogsEnabled: Boolean @deprecated(reason: "Removed - does not do anything") initialOpenInBrowserEnabled: Boolean ip: String + jwtAudience: String + + jwtRefreshExpiry: Duration + + jwtTokenExpiry: Duration + + koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod + + koreaderSyncDeviceId: String + + koreaderSyncPercentageTolerance: Float + + koreaderSyncServerUrl: String + + koreaderSyncStrategy: KoreaderSyncLegacyStrategy @deprecated(reason: "Replaced with koreaderSyncStrategyForward and koreaderSyncStrategyBackward, replace with koreaderSyncStrategyForward, koreaderSyncStrategyBackward") + + koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy + + koreaderSyncStrategyForward: KoreaderSyncConflictStrategy + + koreaderSyncUserkey: String + + koreaderSyncUsername: String + localSourcePath: String + maxLogFileSize: String + + maxLogFiles: Int + + maxLogFolderSize: String + maxSourcesInParallel: Int + opdsChapterSortOrder: SortOrder + + opdsEnablePageReadProgress: Boolean + + opdsItemsPerPage: Int + + opdsMarkAsReadOnDownload: Boolean + + opdsShowOnlyDownloadedChapters: Boolean + + opdsShowOnlyUnreadChapters: Boolean + + opdsUseBinaryFileSizes: Boolean + port: Int socksProxyEnabled: Boolean @@ -1720,24 +2192,52 @@ input PartialSettingsTypeInput { union Preference = CheckBoxPreference|EditTextPreference|ListPreference|MultiSelectListPreference|SwitchPreference +input PullKoSyncProgressInput { + chapterId: Int! + + clientMutationId: String +} + +type PullKoSyncProgressPayload { + chapter: ChapterType + + clientMutationId: String + + syncConflict: SyncConflictInfoType +} + +input PushKoSyncProgressInput { + chapterId: Int! + + clientMutationId: String +} + +type PushKoSyncProgressPayload { + chapter: ChapterType + + clientMutationId: String + + success: Boolean! +} + type Query { restoreStatus(id: String!): BackupRestoreStatus validateBackup(input: ValidateBackupInput!): ValidateBackupResult! - categories(condition: CategoryConditionInput, filter: CategoryFilterInput, orderBy: CategoryOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): CategoryNodeList! + categories(condition: CategoryConditionInput, filter: CategoryFilterInput, orderBy: CategoryOrderBy, orderByType: SortOrder, order: [CategoryOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): CategoryNodeList! category(id: Int!): CategoryType! chapter(id: Int!): ChapterType! - chapters(condition: ChapterConditionInput, filter: ChapterFilterInput, orderBy: ChapterOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): ChapterNodeList! + chapters(condition: ChapterConditionInput, filter: ChapterFilterInput, orderBy: ChapterOrderBy, orderByType: SortOrder, order: [ChapterOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): ChapterNodeList! downloadStatus: DownloadStatus! extension(pkgName: String!): ExtensionType! - extensions(condition: ExtensionConditionInput, filter: ExtensionFilterInput, orderBy: ExtensionOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): ExtensionNodeList! + extensions(condition: ExtensionConditionInput, filter: ExtensionFilterInput, orderBy: ExtensionOrderBy, orderByType: SortOrder, order: [ExtensionOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): ExtensionNodeList! aboutServer: AboutServerPayload! @@ -1749,33 +2249,49 @@ type Query { getWebUIUpdateStatus: WebUIUpdateStatus! + koSyncStatus: KoSyncStatusPayload! + manga(id: Int!): MangaType! - mangas(condition: MangaConditionInput, filter: MangaFilterInput, orderBy: MangaOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): MangaNodeList! + mangas(condition: MangaConditionInput, filter: MangaFilterInput, orderBy: MangaOrderBy, orderByType: SortOrder, order: [MangaOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): MangaNodeList! meta(key: String!): GlobalMetaType! - metas(condition: MetaConditionInput, filter: MetaFilterInput, orderBy: MetaOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): GlobalMetaNodeList! + metas(condition: MetaConditionInput, filter: MetaFilterInput, orderBy: MetaOrderBy, orderByType: SortOrder, order: [MetaOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): GlobalMetaNodeList! settings: SettingsType! source(id: LongString!): SourceType! - sources(condition: SourceConditionInput, filter: SourceFilterInput, orderBy: SourceOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): SourceNodeList! + sources(condition: SourceConditionInput, filter: SourceFilterInput, orderBy: SourceOrderBy, orderByType: SortOrder, order: [SourceOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): SourceNodeList! searchTracker(input: SearchTrackerInput!): SearchTrackerPayload! trackRecord(id: Int!): TrackRecordType! - trackRecords(condition: TrackRecordConditionInput, filter: TrackRecordFilterInput, orderBy: TrackRecordOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): TrackRecordNodeList! + trackRecords(condition: TrackRecordConditionInput, filter: TrackRecordFilterInput, orderBy: TrackRecordOrderBy, orderByType: SortOrder, order: [TrackRecordOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): TrackRecordNodeList! tracker(id: Int!): TrackerType! - trackers(condition: TrackerConditionInput, orderBy: TrackerOrderBy, orderByType: SortOrder, before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): TrackerNodeList! + trackers(condition: TrackerConditionInput, orderBy: TrackerOrderBy, orderByType: SortOrder, order: [TrackerOrderInput!], before: Cursor, after: Cursor, first: Int, last: Int, offset: Int): TrackerNodeList! lastUpdateTimestamp: LastUpdateTimestampPayload! - updateStatus: UpdateStatus! + libraryUpdateStatus: LibraryUpdateStatus! + + updateStatus: UpdateStatus! @deprecated(reason: "Replaced with libraryUpdateStatus, replace with libraryUpdateStatus") +} + +input RefreshTokenInput { + clientMutationId: String + + refreshToken: String! +} + +type RefreshTokenPayload { + accessToken: String! + + clientMutationId: String } input ReorderChapterDownloadInput { @@ -1911,8 +2427,16 @@ type SetSourceMetaPayload { } interface Settings { + authMode: AuthMode + + authPassword: String + + authUsername: String + autoDownloadAheadLimit: Int @deprecated(reason: "Replaced with autoDownloadNewChaptersLimit, replace with autoDownloadNewChaptersLimit") + autoDownloadIgnoreReUploads: Boolean + autoDownloadNewChapters: Boolean autoDownloadNewChaptersLimit: Int @@ -1925,16 +2449,26 @@ interface Settings { backupTime: String - basicAuthEnabled: Boolean + basicAuthEnabled: Boolean @deprecated(reason: "Removed - prefer authMode, replace with authMode") - basicAuthPassword: String + basicAuthPassword: String @deprecated(reason: "Removed - prefer authPassword, replace with authPassword") - basicAuthUsername: String + basicAuthUsername: String @deprecated(reason: "Removed - prefer authUsername, replace with authUsername") + + databasePassword: String + + databaseType: DatabaseType + + databaseUrl: String + + databaseUsername: String debugLogsEnabled: Boolean downloadAsCbz: Boolean + downloadConversions: [SettingsDownloadConversion!] + downloadsPath: String electronPath: String @@ -1949,6 +2483,8 @@ interface Settings { extensionRepos: [String!] + flareSolverrAsResponseFallback: Boolean + flareSolverrEnabled: Boolean flareSolverrSessionName: String @@ -1961,16 +2497,60 @@ interface Settings { globalUpdateInterval: Float - gqlDebugLogsEnabled: Boolean + gqlDebugLogsEnabled: Boolean @deprecated(reason: "Removed - does not do anything") initialOpenInBrowserEnabled: Boolean ip: String + jwtAudience: String + + jwtRefreshExpiry: Duration + + jwtTokenExpiry: Duration + + koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod + + koreaderSyncDeviceId: String + + koreaderSyncPercentageTolerance: Float + + koreaderSyncServerUrl: String + + koreaderSyncStrategy: KoreaderSyncLegacyStrategy @deprecated(reason: "Replaced with koreaderSyncStrategyForward and koreaderSyncStrategyBackward, replace with koreaderSyncStrategyForward, koreaderSyncStrategyBackward") + + koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy + + koreaderSyncStrategyForward: KoreaderSyncConflictStrategy + + koreaderSyncUserkey: String + + koreaderSyncUsername: String + localSourcePath: String + maxLogFileSize: String + + maxLogFiles: Int + + maxLogFolderSize: String + maxSourcesInParallel: Int + opdsChapterSortOrder: SortOrder + + opdsEnablePageReadProgress: Boolean + + opdsItemsPerPage: Int + + opdsMarkAsReadOnDownload: Boolean + + opdsShowOnlyDownloadedChapters: Boolean + + opdsShowOnlyUnreadChapters: Boolean + + opdsUseBinaryFileSizes: Boolean + port: Int socksProxyEnabled: Boolean @@ -1998,9 +2578,41 @@ interface Settings { webUIUpdateCheckInterval: Float } +interface SettingsDownloadConversion { + compressionLevel: Float + + mimeType: String! + + target: String! +} + +type SettingsDownloadConversionType implements SettingsDownloadConversion { + compressionLevel: Float + + mimeType: String! + + target: String! +} + +input SettingsDownloadConversionTypeInput { + compressionLevel: Float + + mimeType: String! + + target: String! +} + type SettingsType implements Settings { + authMode: AuthMode! + + authPassword: String! + + authUsername: String! + autoDownloadAheadLimit: Int! @deprecated(reason: "Replaced with autoDownloadNewChaptersLimit, replace with autoDownloadNewChaptersLimit") + autoDownloadIgnoreReUploads: Boolean! + autoDownloadNewChapters: Boolean! autoDownloadNewChaptersLimit: Int! @@ -2013,16 +2625,26 @@ type SettingsType implements Settings { backupTime: String! - basicAuthEnabled: Boolean! + basicAuthEnabled: Boolean! @deprecated(reason: "Removed - prefer authMode, replace with authMode") - basicAuthPassword: String! + basicAuthPassword: String! @deprecated(reason: "Removed - prefer authPassword, replace with authPassword") - basicAuthUsername: String! + basicAuthUsername: String! @deprecated(reason: "Removed - prefer authUsername, replace with authUsername") + + databasePassword: String! + + databaseType: DatabaseType! + + databaseUrl: String! + + databaseUsername: String! debugLogsEnabled: Boolean! downloadAsCbz: Boolean! + downloadConversions: [SettingsDownloadConversionType!]! + downloadsPath: String! electronPath: String! @@ -2037,6 +2659,8 @@ type SettingsType implements Settings { extensionRepos: [String!]! + flareSolverrAsResponseFallback: Boolean! + flareSolverrEnabled: Boolean! flareSolverrSessionName: String! @@ -2049,16 +2673,60 @@ type SettingsType implements Settings { globalUpdateInterval: Float! - gqlDebugLogsEnabled: Boolean! + gqlDebugLogsEnabled: Boolean! @deprecated(reason: "Removed - does not do anything") initialOpenInBrowserEnabled: Boolean! ip: String! + jwtAudience: String! + + jwtRefreshExpiry: Duration! + + jwtTokenExpiry: Duration! + + koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod! + + koreaderSyncDeviceId: String! + + koreaderSyncPercentageTolerance: Float! + + koreaderSyncServerUrl: String! + + koreaderSyncStrategy: KoreaderSyncLegacyStrategy! @deprecated(reason: "Replaced with koreaderSyncStrategyForward and koreaderSyncStrategyBackward, replace with koreaderSyncStrategyForward, koreaderSyncStrategyBackward") + + koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy! + + koreaderSyncStrategyForward: KoreaderSyncConflictStrategy! + + koreaderSyncUserkey: String! + + koreaderSyncUsername: String! + localSourcePath: String! + maxLogFileSize: String! + + maxLogFiles: Int! + + maxLogFolderSize: String! + maxSourcesInParallel: Int! + opdsChapterSortOrder: SortOrder! + + opdsEnablePageReadProgress: Boolean! + + opdsItemsPerPage: Int! + + opdsMarkAsReadOnDownload: Boolean! + + opdsShowOnlyDownloadedChapters: Boolean! + + opdsShowOnlyUnreadChapters: Boolean! + + opdsUseBinaryFileSizes: Boolean! + port: Int! socksProxyEnabled: Boolean! @@ -2188,6 +2856,12 @@ enum SourceOrderBy { LANG } +input SourceOrderInput { + by: SourceOrderBy! + + byType: SortOrder +} + input SourcePreferenceChangeInput { checkBoxState: Boolean @@ -2203,6 +2877,8 @@ input SourcePreferenceChangeInput { } type SourceType { + baseUrl: String + displayName: String! iconUrl: String! @@ -2253,12 +2929,28 @@ type StopDownloaderPayload { input StringFilterInput { distinctFrom: String + distinctFromAll: [String!] + + distinctFromAny: [String!] + distinctFromInsensitive: String + distinctFromInsensitiveAll: [String!] + + distinctFromInsensitiveAny: [String!] + endsWith: String + endsWithAll: [String!] + + endsWithAny: [String!] + endsWithInsensitive: String + endsWithInsensitiveAll: [String!] + + endsWithInsensitiveAny: [String!] + equalTo: String greaterThan: String @@ -2275,8 +2967,16 @@ input StringFilterInput { includes: String + includesAll: [String!] + + includesAny: [String!] + includesInsensitive: String + includesInsensitiveAll: [String!] + + includesInsensitiveAny: [String!] + isNull: Boolean lessThan: String @@ -2289,45 +2989,101 @@ input StringFilterInput { like: String + likeAll: [String!] + + likeAny: [String!] + likeInsensitive: String + likeInsensitiveAll: [String!] + + likeInsensitiveAny: [String!] + notDistinctFrom: String notDistinctFromInsensitive: String notEndsWith: String + notEndsWithAll: [String!] + + notEndsWithAny: [String!] + notEndsWithInsensitive: String + notEndsWithInsensitiveAll: [String!] + + notEndsWithInsensitiveAny: [String!] + notEqualTo: String + notEqualToAll: [String!] + + notEqualToAny: [String!] + notIn: [String!] notInInsensitive: [String!] notIncludes: String + notIncludesAll: [String!] + + notIncludesAny: [String!] + notIncludesInsensitive: String + notIncludesInsensitiveAll: [String!] + + notIncludesInsensitiveAny: [String!] + notLike: String + notLikeAll: [String!] + + notLikeAny: [String!] + notLikeInsensitive: String + notLikeInsensitiveAll: [String!] + + notLikeInsensitiveAny: [String!] + notStartsWith: String + notStartsWithAll: [String!] + + notStartsWithAny: [String!] + notStartsWithInsensitive: String + notStartsWithInsensitiveAll: [String!] + + notStartsWithInsensitiveAny: [String!] + startsWith: String + startsWithAll: [String!] + + startsWithAny: [String!] + startsWithInsensitive: String + + startsWithInsensitiveAll: [String!] + + startsWithInsensitiveAny: [String!] } type Subscription { - downloadChanged: DownloadStatus! + downloadChanged: DownloadStatus! @deprecated(reason: "Replaced with downloadStatusChanged, replace with downloadStatusChanged(input)") + + downloadStatusChanged(input: DownloadChangedInput!): DownloadUpdates! webUIUpdateStatusChange: WebUIUpdateStatus! - updateStatusChanged: UpdateStatus! + libraryUpdateStatusChanged(input: LibraryUpdateStatusChangedInput!): UpdaterUpdates! + + updateStatusChanged: UpdateStatus! @deprecated(reason: "Replaced with updates, replace with updates(input)") } type SwitchPreference { @@ -2335,21 +3091,41 @@ type SwitchPreference { default: Boolean! - key: String! + enabled: Boolean! + + key: String summary: String - title: String! + title: String visible: Boolean! } +type SyncConflictInfoType { + deviceName: String! + + remotePage: Int! +} + type TextFilter { default: String! name: String! } +input TrackProgressInput { + clientMutationId: String + + mangaId: Int! +} + +type TrackProgressPayload { + clientMutationId: String + + trackRecords: [TrackRecordType!]! +} + input TrackRecordConditionInput { finishDate: LongString @@ -2361,6 +3137,8 @@ input TrackRecordConditionInput { mangaId: Int + private: Boolean + remoteId: LongString remoteUrl: String @@ -2401,6 +3179,8 @@ input TrackRecordFilterInput { or: [TrackRecordFilterInput!] + private: BooleanFilterInput + remoteId: LongFilterInput remoteUrl: StringFilterInput @@ -2448,6 +3228,14 @@ enum TrackRecordOrderBy { START_DATE FINISH_DATE + + PRIVATE +} + +input TrackRecordOrderInput { + by: TrackRecordOrderBy! + + byType: SortOrder } type TrackRecordType { @@ -2461,6 +3249,8 @@ type TrackRecordType { mangaId: Int! + private: Boolean! + remoteId: LongString! remoteUrl: String! @@ -2487,16 +3277,30 @@ type TrackRecordType { type TrackSearchType { coverUrl: String! + finishedReadingDate: LongString! + id: Int! + lastChapterRead: Float! + + libraryId: LongString + + private: Boolean! + publishingStatus: String! publishingType: String! remoteId: LongString! + score: Float! + startDate: String! + startedReadingDate: LongString! + + status: Int! + summary: String! title: String! @@ -2507,6 +3311,8 @@ type TrackSearchType { trackingUrl: String! + displayScore: String! + tracker: TrackerType! } @@ -2550,6 +3356,12 @@ enum TrackerOrderBy { IS_LOGGED_IN } +input TrackerOrderInput { + by: TrackerOrderBy! + + byType: SortOrder +} + type TrackerType { authUrl: String @@ -2561,6 +3373,12 @@ type TrackerType { name: String! + supportsPrivateTracking: Boolean! + + supportsReadingDates: Boolean! + + supportsTrackDeletion: Boolean! + isTokenExpired: Boolean! scores: [String!]! @@ -2584,6 +3402,23 @@ type TriStateFilter { name: String! } +input UnbindTrackInput { + clientMutationId: String + + """ + This will only work if the tracker of the track record supports deleting tracks + """ + deleteRemoteTrack: Boolean + + recordId: Int! +} + +type UnbindTrackPayload { + clientMutationId: String + + trackRecord: TrackRecordType +} + input UpdateCategoriesInput { clientMutationId: String @@ -2720,6 +3555,12 @@ type UpdateExtensionsPayload { extensions: [ExtensionType!]! } +input UpdateLibraryInput { + categories: [Int!] + + clientMutationId: String +} + input UpdateLibraryMangaInput { clientMutationId: String } @@ -2730,6 +3571,12 @@ type UpdateLibraryMangaPayload { updateStatus: UpdateStatus! } +type UpdateLibraryPayload { + clientMutationId: String + + updateStatus: LibraryUpdateStatus! +} + input UpdateMangaCategoriesInput { clientMutationId: String @@ -2867,19 +3714,30 @@ enum UpdateStrategy { input UpdateTrackInput { clientMutationId: String + """ + This will only work if the tracker of the track record supports reading dates + """ finishDate: LongString lastChapterRead: Float + """ + This will only work if the tracker of the track record supports private tracking + """ + private: Boolean + recordId: Int! scoreString: String + """ + This will only work if the tracker of the track record supports reading dates + """ startDate: LongString status: Int - unbind: Boolean + unbind: Boolean @deprecated(reason: "Replaced with \"unbindTrack\" mutation, replace with unbindTrack") } type UpdateTrackPayload { @@ -2888,6 +3746,36 @@ type UpdateTrackPayload { trackRecord: TrackRecordType } +type UpdaterJobsInfoType { + finishedJobs: Int! + + isRunning: Boolean! + + skippedCategoriesCount: Int! + + skippedMangasCount: Int! + + totalJobs: Int! +} + +type UpdaterUpdates { + categoryUpdates: [CategoryUpdateType!]! + + """ + The current update status at the time of sending the initial message. Is null for all following messages + """ + initial: LibraryUpdateStatus + + jobsInfo: UpdaterJobsInfoType! + + mangaUpdates: [MangaUpdateType!]! + + """ + Indicates whether updates have been omitted based on the "maxUpdates" subscription variable. In case updates have been omitted, the "updateStatus" query should be re-fetched. + """ + omittedUpdates: Boolean! +} + """ A file part in a multipart request """ @@ -2899,6 +3787,8 @@ input ValidateBackupInput { type ValidateBackupResult { missingSources: [ValidateBackupSource!]! + + missingTrackers: [ValidateBackupTracker!]! } type ValidateBackupSource { @@ -2907,6 +3797,10 @@ type ValidateBackupSource { name: String! } +type ValidateBackupTracker { + name: String! +} + enum WebUIChannel { BUNDLED @@ -2930,7 +3824,7 @@ enum WebUIInterface { } type WebUIUpdateCheck { - channel: String! + channel: WebUIChannel! tag: String! @@ -2938,7 +3832,7 @@ type WebUIUpdateCheck { } type WebUIUpdateInfo { - channel: String! + channel: WebUIChannel! tag: String! } @@ -2961,8 +3855,6 @@ type WebUIUpdateStatus { state: UpdateState! } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition type __Directive { """ The __Directive type represents a Directive that a server supports. @@ -2978,8 +3870,6 @@ type __Directive { args(includeDeprecated: Boolean = false): [__InputValue!]! } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ An enum describing valid locations where a directive can be placed """ @@ -3080,8 +3970,6 @@ enum __DirectiveLocation { INPUT_FIELD_DEFINITION } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition type __EnumValue { name: String! @@ -3092,8 +3980,6 @@ type __EnumValue { deprecationReason: String } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition type __Field { name: String! @@ -3108,8 +3994,6 @@ type __Field { deprecationReason: String } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition type __InputValue { name: String! @@ -3124,8 +4008,6 @@ type __InputValue { deprecationReason: String } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations. """ @@ -3158,8 +4040,6 @@ type __Schema { subscriptionType: __Type } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition type __Type { kind: __TypeKind! @@ -3179,13 +4059,16 @@ type __Type { ofType: __Type + """ + This field is considered experimental because it has not yet been ratified in the graphql specification + """ + isOneOf: Boolean + specifiedByURL: String specifiedByUrl: String @deprecated(reason: "This legacy name has been replaced by `specifiedByURL`") } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ An enum describing what kind of type a given __Type is """ @@ -3231,34 +4114,36 @@ enum __TypeKind { NON_NULL } -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ Directs the executor to include this field or fragment only when the `if` argument is true """ directive @include ("Included when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ Directs the executor to skip this field or fragment when the `if` argument is true. """ directive @skip ("Skipped when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition +""" +Requires user authentication +""" +directive @requireAuth on OBJECT|FIELD_DEFINITION + """ Marks the field, argument, input field or enum value as deprecated """ directive @deprecated ("The reason for the deprecation" reason: String = "No longer supported") on FIELD_DEFINITION|ARGUMENT_DEFINITION|ENUM_VALUE|INPUT_FIELD_DEFINITION -# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 -# noinspection GraphQLTypeRedefinition """ Exposes a URL that specifies the behaviour of this scalar. """ directive @specifiedBy ("The URL that specifies the behaviour of this scalar." url: String!) on SCALAR +""" +Indicates an Input Object is a OneOf Input Object. +""" +directive @oneOf on INPUT_OBJECT + schema { query: Query mutation: Mutation diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt index 5bad9d47..6e8ea149 100644 --- a/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/DataComponent.kt @@ -8,13 +8,29 @@ package ca.gosyer.jui.data import ca.gosyer.jui.core.lang.addSuffix import ca.gosyer.jui.data.backup.BackupRepositoryImpl +import ca.gosyer.jui.data.category.CategoryRepositoryImpl import ca.gosyer.jui.data.chapter.ChapterRepositoryImpl +import ca.gosyer.jui.data.download.DownloadRepositoryImpl +import ca.gosyer.jui.data.extension.ExtensionRepositoryImpl +import ca.gosyer.jui.data.global.GlobalRepositoryImpl +import ca.gosyer.jui.data.library.LibraryRepositoryImpl +import ca.gosyer.jui.data.manga.MangaRepositoryImpl import ca.gosyer.jui.data.settings.SettingsRepositoryImpl +import ca.gosyer.jui.data.source.SourceRepositoryImpl +import ca.gosyer.jui.data.updates.UpdatesRepositoryImpl import ca.gosyer.jui.domain.backup.service.BackupRepository +import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.chapter.service.ChapterRepository +import ca.gosyer.jui.domain.download.service.DownloadRepository +import ca.gosyer.jui.domain.extension.service.ExtensionRepository +import ca.gosyer.jui.domain.global.service.GlobalRepository +import ca.gosyer.jui.domain.library.service.LibraryRepository +import ca.gosyer.jui.domain.manga.service.MangaRepository import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.service.ServerPreferences import ca.gosyer.jui.domain.settings.service.SettingsRepository +import ca.gosyer.jui.domain.source.service.SourceRepository +import ca.gosyer.jui.domain.updates.service.UpdatesRepository import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.network.ws.GraphQLWsProtocol import com.apollographql.ktor.ktorClient @@ -55,6 +71,13 @@ interface DataComponent : SharedDataComponent { @Provides fun settingsRepository(apolloClient: ApolloClient): SettingsRepository = SettingsRepositoryImpl(apolloClient) + @Provides + fun categoryRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): CategoryRepository = CategoryRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + @Provides fun chapterRepository( apolloClient: ApolloClient, @@ -62,6 +85,55 @@ interface DataComponent : SharedDataComponent { serverPreferences: ServerPreferences, ): ChapterRepository = ChapterRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + @Provides + fun downloadRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): DownloadRepository = DownloadRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun extensionRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): ExtensionRepository = ExtensionRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun globalRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): GlobalRepository = GlobalRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun libraryRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): LibraryRepository = LibraryRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun mangaRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): MangaRepository = MangaRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun sourceRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): SourceRepository = SourceRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + + @Provides + fun updatesRepository( + apolloClient: ApolloClient, + http: Http, + serverPreferences: ServerPreferences, + ): UpdatesRepository = UpdatesRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) + @Provides fun backupRepository( apolloClient: ApolloClient, diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/backup/BackupRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/backup/BackupRepositoryImpl.kt index c4753495..1770313d 100644 --- a/data/src/commonMain/kotlin/ca/gosyer/jui/data/backup/BackupRepositoryImpl.kt +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/backup/BackupRepositoryImpl.kt @@ -111,6 +111,8 @@ class BackupRepositoryImpl( BackupRestoreState.FAILURE -> RestoreState.FAILURE BackupRestoreState.RESTORING_CATEGORIES -> RestoreState.RESTORING_CATEGORIES BackupRestoreState.RESTORING_MANGA -> RestoreState.RESTORING_MANGA + BackupRestoreState.RESTORING_META -> RestoreState.RESTORING_META + BackupRestoreState.RESTORING_SETTINGS -> RestoreState.RESTORING_SETTINGS BackupRestoreState.UNKNOWN__ -> RestoreState.UNKNOWN }, mangaProgress, diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/category/CategoryRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/category/CategoryRepositoryImpl.kt new file mode 100644 index 00000000..be5c5003 --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/category/CategoryRepositoryImpl.kt @@ -0,0 +1,173 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.data.category + +import ca.gosyer.jui.data.graphql.AddMangaToCategoriesMutation +import ca.gosyer.jui.data.graphql.CreateCategoryMutation +import ca.gosyer.jui.data.graphql.DeleteCategoryMutation +import ca.gosyer.jui.data.graphql.GetCategoriesQuery +import ca.gosyer.jui.data.graphql.GetCategoryMangaQuery +import ca.gosyer.jui.data.graphql.GetMangaCategoriesQuery +import ca.gosyer.jui.data.graphql.ModifyCategoryMutation +import ca.gosyer.jui.data.graphql.RemoveMangaFromCategoriesMutation +import ca.gosyer.jui.data.graphql.ReorderCategoryMutation +import ca.gosyer.jui.data.graphql.SetCategoryMetaMutation +import ca.gosyer.jui.data.graphql.fragment.CategoryFragment +import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga +import ca.gosyer.jui.domain.category.model.Category +import ca.gosyer.jui.domain.category.model.CategoryMeta +import ca.gosyer.jui.domain.category.service.CategoryRepository +import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class CategoryRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +): CategoryRepository { + override fun getMangaCategories(mangaId: Long): Flow> { + return apolloClient.query( + GetMangaCategoriesQuery(mangaId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.manga.categories.nodes.map { it.categoryFragment.toCategory() } + } + } + + override fun addMangaToCategory( + mangaId: Long, + categoryId: Long, + ): Flow { + return apolloClient.mutation( + AddMangaToCategoriesMutation(mangaId.toInt(), listOf(categoryId.toInt())) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateMangaCategories!!.clientMutationId + } + } + + override fun removeMangaFromCategory( + mangaId: Long, + categoryId: Long, + ): Flow { + return apolloClient.mutation( + RemoveMangaFromCategoriesMutation(mangaId.toInt(), listOf(categoryId.toInt())) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateMangaCategories!!.clientMutationId + } + } + + override fun getCategories(): Flow> { + return apolloClient.query( + GetCategoriesQuery() + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.categories.nodes.map { it.categoryFragment.toCategory() } + } + } + + override fun createCategory(name: String): Flow { + return apolloClient.mutation( + CreateCategoryMutation(name) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.createCategory!!.clientMutationId + } + } + + override fun modifyCategory( + categoryId: Long, + name: String, + ): Flow { + return apolloClient.mutation( + ModifyCategoryMutation(categoryId.toInt(), name) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateCategory!!.clientMutationId + } + } + + override fun reorderCategory( + categoryId: Long, + position: Int, + ): Flow { + return apolloClient.mutation( + ReorderCategoryMutation(categoryId.toInt(), position) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateCategoryOrder!!.clientMutationId + } + } + + override fun deleteCategory(categoryId: Long): Flow { + return apolloClient.mutation( + DeleteCategoryMutation(categoryId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.deleteCategory!!.clientMutationId + } + } + + override fun getMangaFromCategory(categoryId: Long): Flow> { + return apolloClient.query( + GetCategoryMangaQuery(listOf(categoryId.toInt())) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.mangas.nodes.map { it.mangaFragment.toManga() } + } + } + + override fun updateCategoryMeta( + categoryId: Long, + key: String, + value: String, + ): Flow { + return apolloClient.mutation( + SetCategoryMetaMutation(categoryId.toInt(), key, value) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.setCategoryMeta!!.clientMutationId + } + } + + companion object { + internal fun CategoryFragment.toCategory(): Category { + return Category( + id = id.toLong(), + order = order, + name = name, + default = default, + meta = CategoryMeta() + ) + } + } +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/chapter/ChapterRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/chapter/ChapterRepositoryImpl.kt index b3497377..21edfcf4 100644 --- a/data/src/commonMain/kotlin/ca/gosyer/jui/data/chapter/ChapterRepositoryImpl.kt +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/chapter/ChapterRepositoryImpl.kt @@ -10,11 +10,14 @@ import ca.gosyer.jui.data.graphql.UpdateChapterMetaMutation import ca.gosyer.jui.data.graphql.UpdateChapterMutation import ca.gosyer.jui.data.graphql.UpdateChaptersMutation import ca.gosyer.jui.data.graphql.fragment.ChapterFragment +import ca.gosyer.jui.data.graphql.fragment.ChapterWithMangaFragment import ca.gosyer.jui.data.graphql.type.UpdateChapterPatchInput +import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.chapter.model.ChapterMeta import ca.gosyer.jui.domain.chapter.service.ChapterRepository import ca.gosyer.jui.domain.server.Http +import ca.gosyer.jui.domain.updates.model.MangaAndChapter import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.Optional import io.ktor.client.request.HttpRequestBuilder @@ -153,7 +156,7 @@ class ChapterRepositoryImpl( .toFlow() .map { val chapters = it.dataAssertNoErrors - chapters.fetchChapters.chapters.map { it.chapterFragment.toChapter() } + chapters.fetchChapters!!.chapters.map { it.chapterFragment.toChapter() } } } @@ -166,7 +169,7 @@ class ChapterRepositoryImpl( .toFlow() .map { val chapters = it.dataAssertNoErrors - chapters.fetchChapterPages.pages + chapters.fetchChapterPages!!.pages } } @@ -204,5 +207,12 @@ class ChapterRepositoryImpl( ) ) } + + internal fun ChapterWithMangaFragment.toMangaAndChapter(): MangaAndChapter { + return MangaAndChapter( + manga.mangaFragment.toManga(), + chapterFragment.toChapter(), + ) + } } } diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/download/DownloadRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/download/DownloadRepositoryImpl.kt new file mode 100644 index 00000000..1a1afd75 --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/download/DownloadRepositoryImpl.kt @@ -0,0 +1,102 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.data.download + +import ca.gosyer.jui.data.graphql.ClearDownloaderMutation +import ca.gosyer.jui.data.graphql.DequeueChapterDownloadMutation +import ca.gosyer.jui.data.graphql.EnqueueChapterDownloadMutation +import ca.gosyer.jui.data.graphql.EnqueueChapterDownloadsMutation +import ca.gosyer.jui.data.graphql.ReorderChapterDownloadMutation +import ca.gosyer.jui.data.graphql.StartDownloaderMutation +import ca.gosyer.jui.data.graphql.StopDownloaderMutation +import ca.gosyer.jui.domain.download.service.DownloadRepository +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DownloadRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +): DownloadRepository { + override fun startDownloading(): Flow { + return apolloClient.mutation( + StartDownloaderMutation() + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun stopDownloading(): Flow { + return apolloClient.mutation( + StopDownloaderMutation() + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun clearDownloadQueue(): Flow { + return apolloClient.mutation( + ClearDownloaderMutation() + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun queueChapterDownload(chapterId: Long): Flow { + return apolloClient.mutation( + EnqueueChapterDownloadMutation(chapterId.toInt()) + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun stopChapterDownload(chapterId: Long): Flow { + return apolloClient.mutation( + DequeueChapterDownloadMutation(chapterId.toInt()) + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun reorderChapterDownload( + chapterId: Long, + to: Int, + ): Flow { + return apolloClient.mutation( + ReorderChapterDownloadMutation(chapterId.toInt(), to) + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + override fun batchDownload(chapterIds: List): Flow { + return apolloClient.mutation( + EnqueueChapterDownloadsMutation(chapterIds.map { it.toInt() }) + ) + .toFlow() + .map { + it.dataAssertNoErrors + } + } + + +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/extension/ExtensionRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/extension/ExtensionRepositoryImpl.kt new file mode 100644 index 00000000..89f0599a --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/extension/ExtensionRepositoryImpl.kt @@ -0,0 +1,105 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.data.extension + +import ca.gosyer.jui.data.graphql.FetchExtensionsMutation +import ca.gosyer.jui.data.graphql.InstallExtensionMutation +import ca.gosyer.jui.data.graphql.InstallExternalExtensionMutation +import ca.gosyer.jui.data.graphql.UninstallExtensionMutation +import ca.gosyer.jui.data.graphql.UpdateExtensionMutation +import ca.gosyer.jui.data.graphql.fragment.ExtensionFragment +import ca.gosyer.jui.domain.extension.model.Extension +import ca.gosyer.jui.domain.extension.service.ExtensionRepository +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.DefaultUpload +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import okio.Source + +class ExtensionRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +): ExtensionRepository { + override fun getExtensionList(): Flow> { + return apolloClient.mutation( + FetchExtensionsMutation() + ) + .toFlow() + .map { + it.dataAssertNoErrors.fetchExtensions!!.extensions.map { it.extensionFragment.toExtension() } + } + } + + override fun installExtension(source: Source): Flow { + return apolloClient.mutation( + InstallExternalExtensionMutation( + DefaultUpload.Builder() + .content { + it.writeAll(source) + } + .fileName("extension.apk") + .contentType("application/octet-stream") + .build() + ) + ) + .toFlow() + .map { + it.dataAssertNoErrors.installExternalExtension!! + } + } + + override fun installExtension(pkgName: String): Flow { + return apolloClient.mutation( + InstallExtensionMutation(pkgName) + ) + .toFlow() + .map { + it.dataAssertNoErrors.updateExtension + } + } + + override fun updateExtension(pkgName: String): Flow { + return apolloClient.mutation( + UpdateExtensionMutation(pkgName) + ) + .toFlow() + .map { + it.dataAssertNoErrors.updateExtension + } + } + + override fun uninstallExtension(pkgName: String): Flow { + return apolloClient.mutation( + UninstallExtensionMutation(pkgName) + ) + .toFlow() + .map { + it.dataAssertNoErrors.updateExtension + } + } + + companion object { + internal fun ExtensionFragment.toExtension(): Extension { + return Extension( + name = name, + pkgName = pkgName, + versionName = versionName, + versionCode = versionCode, + lang = lang, + apkName = apkName, + iconUrl = iconUrl, + installed = isInstalled, + hasUpdate = hasUpdate, + obsolete = isObsolete, + isNsfw = isNsfw, + ) + } + } +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/global/GlobalRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/global/GlobalRepositoryImpl.kt new file mode 100644 index 00000000..da7e9730 --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/global/GlobalRepositoryImpl.kt @@ -0,0 +1,47 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.data.global + +import ca.gosyer.jui.data.graphql.GetGlobalMetaQuery +import ca.gosyer.jui.data.graphql.SetGlobalMetaMutation +import ca.gosyer.jui.domain.global.model.GlobalMeta +import ca.gosyer.jui.domain.global.service.GlobalRepository +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GlobalRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +): GlobalRepository { + + override fun getGlobalMeta(): Flow { + return apolloClient.query( + GetGlobalMetaQuery() + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + GlobalMeta(data.metas.nodes.find { it.key == "example" }?.value?.toIntOrNull() ?: 0) + } + } + + override fun updateGlobalMeta(key: String, value: String): Flow { + return apolloClient.mutation( + SetGlobalMetaMutation(key, value) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.setGlobalMeta!!.clientMutationId + } + } + +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/library/LibraryRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/library/LibraryRepositoryImpl.kt new file mode 100644 index 00000000..a5f749c7 --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/library/LibraryRepositoryImpl.kt @@ -0,0 +1,34 @@ +package ca.gosyer.jui.data.library + +import ca.gosyer.jui.data.graphql.SetMangaInLibraryMutation +import ca.gosyer.jui.domain.library.service.LibraryRepository +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class LibraryRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +) : LibraryRepository { + fun setMangaInLibrary(mangaId: Long, inLibrary: Boolean): Flow { + return apolloClient.mutation( + SetMangaInLibraryMutation(mangaId.toInt(), inLibrary) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateManga!!.clientMutationId + } + } + + override fun addMangaToLibrary(mangaId: Long): Flow { + return setMangaInLibrary(mangaId, true) + } + + override fun removeMangaFromLibrary(mangaId: Long): Flow { + return setMangaInLibrary(mangaId, false) + } +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/manga/MangaRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/manga/MangaRepositoryImpl.kt new file mode 100644 index 00000000..48ff33d5 --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/manga/MangaRepositoryImpl.kt @@ -0,0 +1,188 @@ +package ca.gosyer.jui.data.manga + +import ca.gosyer.jui.data.graphql.GetMangaLibraryQuery +import ca.gosyer.jui.data.graphql.GetMangaQuery +import ca.gosyer.jui.data.graphql.GetThumbnailUrlQuery +import ca.gosyer.jui.data.graphql.RefreshMangaMutation +import ca.gosyer.jui.data.graphql.SetMangaMetaMutation +import ca.gosyer.jui.data.graphql.fragment.LibraryMangaFragment +import ca.gosyer.jui.data.graphql.fragment.MangaFragment +import ca.gosyer.jui.data.source.SourceRepositoryImpl.Companion.toSource +import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.manga.model.MangaMeta +import ca.gosyer.jui.domain.manga.model.MangaStatus +import ca.gosyer.jui.domain.manga.model.UpdateStrategy +import ca.gosyer.jui.domain.manga.service.MangaRepository +import ca.gosyer.jui.domain.server.Http +import com.apollographql.apollo.ApolloClient +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsChannel +import io.ktor.http.Url +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import ca.gosyer.jui.data.graphql.type.MangaStatus as GqlMangaStatus +import ca.gosyer.jui.data.graphql.type.UpdateStrategy as GqlUpdateStrategy + +class MangaRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +) : MangaRepository { + override fun getManga(mangaId: Long): Flow { + return apolloClient.query( + GetMangaQuery(mangaId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.manga.mangaFragment.toManga() + } + } + + override fun refreshManga(mangaId: Long): Flow { + return apolloClient.mutation( + RefreshMangaMutation(mangaId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.fetchManga!!.manga.mangaFragment.toManga() + } + } + + override fun getMangaLibrary(mangaId: Long): Flow { + return apolloClient.query( + GetMangaLibraryQuery(mangaId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.manga.libraryMangaFragment.toManga() + } + } + + override fun getMangaThumbnail( + mangaId: Long, + block: HttpRequestBuilder.() -> Unit, + ): Flow { + return apolloClient.query( + GetThumbnailUrlQuery(mangaId.toInt()) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + http.get(data.manga.thumbnailUrl!!).bodyAsChannel() + } + } + + override fun updateMangaMeta( + mangaId: Long, + key: String, + value: String, + ): Flow { + return apolloClient.mutation( + SetMangaMetaMutation(mangaId.toInt(), key, value) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.setMangaMeta!!.clientMutationId + } + } + + companion object { + internal fun MangaFragment.toManga(): Manga { + return Manga( + id = id.toLong(), + sourceId = sourceId, + url = url, + title = title, + thumbnailUrl = thumbnailUrl, + thumbnailUrlLastFetched = thumbnailUrlLastFetched ?: 0, + initialized = initialized, + artist = artist, + author = author, + description = description, + genre = genre, + status = when (status) { + GqlMangaStatus.ONGOING -> MangaStatus.ONGOING + GqlMangaStatus.COMPLETED -> MangaStatus.COMPLETED + GqlMangaStatus.LICENSED -> MangaStatus.LICENSED + GqlMangaStatus.PUBLISHING_FINISHED -> MangaStatus.PUBLISHING_FINISHED + GqlMangaStatus.CANCELLED -> MangaStatus.CANCELLED + GqlMangaStatus.ON_HIATUS -> MangaStatus.ON_HIATUS + GqlMangaStatus.UNKNOWN, GqlMangaStatus.UNKNOWN__ -> MangaStatus.UNKNOWN + }, + inLibrary = inLibrary, + source = null, + updateStrategy = when (updateStrategy) { + GqlUpdateStrategy.ALWAYS_UPDATE -> UpdateStrategy.ALWAYS_UPDATE + GqlUpdateStrategy.ONLY_FETCH_ONCE -> UpdateStrategy.ONLY_FETCH_ONCE + GqlUpdateStrategy.UNKNOWN__ -> UpdateStrategy.ALWAYS_UPDATE + }, + freshData = false, + meta = MangaMeta( + meta.find { it.key == "juiReaderMode" }?.value.orEmpty(), + ), + realUrl = realUrl, + lastFetchedAt = lastFetchedAt, + chaptersLastFetchedAt = chaptersLastFetchedAt, + inLibraryAt = inLibraryAt, + unreadCount = null, + downloadCount = null, + chapterCount = null, + lastChapterReadTime = null, + age = age, + chaptersAge = null, + ) + } + + internal fun LibraryMangaFragment.toManga(): Manga { + return Manga( + id = id.toLong(), + sourceId = sourceId, + url = url, + title = title, + thumbnailUrl = thumbnailUrl, + thumbnailUrlLastFetched = thumbnailUrlLastFetched ?: 0, + initialized = initialized, + artist = artist, + author = author, + description = description, + genre = genre, + status = when (status) { + GqlMangaStatus.ONGOING -> MangaStatus.ONGOING + GqlMangaStatus.COMPLETED -> MangaStatus.COMPLETED + GqlMangaStatus.LICENSED -> MangaStatus.LICENSED + GqlMangaStatus.PUBLISHING_FINISHED -> MangaStatus.PUBLISHING_FINISHED + GqlMangaStatus.CANCELLED -> MangaStatus.CANCELLED + GqlMangaStatus.ON_HIATUS -> MangaStatus.ON_HIATUS + GqlMangaStatus.UNKNOWN, GqlMangaStatus.UNKNOWN__ -> MangaStatus.UNKNOWN + }, + inLibrary = inLibrary, + source = source?.sourceFragment?.toSource(), + updateStrategy = when (updateStrategy) { + GqlUpdateStrategy.ALWAYS_UPDATE -> UpdateStrategy.ALWAYS_UPDATE + GqlUpdateStrategy.ONLY_FETCH_ONCE -> UpdateStrategy.ONLY_FETCH_ONCE + GqlUpdateStrategy.UNKNOWN__ -> UpdateStrategy.ALWAYS_UPDATE + }, + freshData = false, + meta = MangaMeta( + meta.find { it.key == "juiReaderMode" }?.value.orEmpty(), + ), + realUrl = realUrl, + lastFetchedAt = lastFetchedAt, + chaptersLastFetchedAt = chaptersLastFetchedAt, + inLibraryAt = inLibraryAt, + unreadCount = unreadCount, + downloadCount = downloadCount, + chapterCount = chapters.totalCount, + lastChapterReadTime = lastReadChapter?.lastReadAt ?: 0, + age = age, + chaptersAge = null, + ) + } + } +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/settings/SettingsRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/settings/SettingsRepositoryImpl.kt index 216e1dad..3ab6ce5e 100644 --- a/data/src/commonMain/kotlin/ca/gosyer/jui/data/settings/SettingsRepositoryImpl.kt +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/settings/SettingsRepositoryImpl.kt @@ -9,6 +9,13 @@ package ca.gosyer.jui.data.settings import ca.gosyer.jui.data.graphql.AllSettingsQuery import ca.gosyer.jui.data.graphql.SetSettingsMutation import ca.gosyer.jui.data.graphql.fragment.SettingsTypeFragment +import ca.gosyer.jui.data.graphql.fragment.SettingsTypeFragment.DownloadConversion +import ca.gosyer.jui.data.graphql.type.AuthMode +import ca.gosyer.jui.data.graphql.type.DatabaseType +import ca.gosyer.jui.data.graphql.type.KoreaderSyncChecksumMethod +import ca.gosyer.jui.data.graphql.type.KoreaderSyncConflictStrategy +import ca.gosyer.jui.data.graphql.type.SettingsDownloadConversionTypeInput +import ca.gosyer.jui.data.graphql.type.SortOrder import ca.gosyer.jui.data.graphql.type.WebUIChannel import ca.gosyer.jui.data.graphql.type.WebUIFlavor import ca.gosyer.jui.data.graphql.type.WebUIInterface @@ -22,6 +29,12 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import ca.gosyer.jui.domain.settings.model.AuthMode as DomainAuthMode +import ca.gosyer.jui.domain.settings.model.DatabaseType as DomainDatabaseType +import ca.gosyer.jui.domain.settings.model.DownloadConversion as DomainDownloadConversion +import ca.gosyer.jui.domain.settings.model.KoreaderSyncChecksumMethod as DomainKoreaderSyncChecksumMethod +import ca.gosyer.jui.domain.settings.model.KoreaderSyncConflictStrategy as DomainKoreaderSyncConflictStrategy +import ca.gosyer.jui.domain.settings.model.SortOrder as DomainSortOrder import ca.gosyer.jui.domain.settings.model.WebUIChannel as DomainWebUIChannel import ca.gosyer.jui.domain.settings.model.WebUIFlavor as DomainWebUIFlavor import ca.gosyer.jui.domain.settings.model.WebUIInterface as DomainWebUIInterface @@ -31,17 +44,23 @@ class SettingsRepositoryImpl( ) : SettingsRepository { private fun SettingsTypeFragment.toSettings() = Settings( + authMode = authMode.toDomain(), + authPassword = authPassword, + authUsername = authUsername, + autoDownloadIgnoreReUploads = autoDownloadIgnoreReUploads, autoDownloadNewChapters = autoDownloadNewChapters, autoDownloadNewChaptersLimit = autoDownloadNewChaptersLimit, backupInterval = backupInterval, backupPath = backupPath, backupTTL = backupTTL, backupTime = backupTime, - basicAuthEnabled = basicAuthEnabled, - basicAuthPassword = basicAuthPassword, - basicAuthUsername = basicAuthUsername, + databasePassword = databasePassword, + databaseType = databaseType.toDomain(), + databaseUrl = databaseUrl, + databaseUsername = databaseUsername, debugLogsEnabled = debugLogsEnabled, downloadAsCbz = downloadAsCbz, + downloadConversions = downloadConversions.map { it.toDomain() }, downloadsPath = downloadsPath, electronPath = electronPath, excludeCompleted = excludeCompleted, @@ -49,17 +68,38 @@ class SettingsRepositoryImpl( excludeNotStarted = excludeNotStarted, excludeUnreadChapters = excludeUnreadChapters, extensionRepos = extensionRepos, + flareSolverrAsResponseFallback = flareSolverrAsResponseFallback, flareSolverrEnabled = flareSolverrEnabled, flareSolverrSessionName = flareSolverrSessionName, flareSolverrSessionTtl = flareSolverrSessionTtl, flareSolverrTimeout = flareSolverrTimeout, flareSolverrUrl = flareSolverrUrl, globalUpdateInterval = globalUpdateInterval, - gqlDebugLogsEnabled = gqlDebugLogsEnabled, initialOpenInBrowserEnabled = initialOpenInBrowserEnabled, ip = ip, + jwtAudience = jwtAudience, + jwtRefreshExpiry = jwtRefreshExpiry, + jwtTokenExpiry = jwtTokenExpiry, + koreaderSyncChecksumMethod = koreaderSyncChecksumMethod.toDomain(), + koreaderSyncDeviceId = koreaderSyncDeviceId, + koreaderSyncPercentageTolerance = koreaderSyncPercentageTolerance, + koreaderSyncServerUrl = koreaderSyncServerUrl, + koreaderSyncStrategyBackward = koreaderSyncStrategyBackward.toDomain(), + koreaderSyncStrategyForward = koreaderSyncStrategyForward.toDomain(), + koreaderSyncUserkey = koreaderSyncUserkey, + koreaderSyncUsername = koreaderSyncUsername, localSourcePath = localSourcePath, + maxLogFileSize = maxLogFileSize, + maxLogFiles = maxLogFiles, + maxLogFolderSize = maxLogFolderSize, maxSourcesInParallel = maxSourcesInParallel, + opdsChapterSortOrder = opdsChapterSortOrder.toDomain(), + opdsEnablePageReadProgress = opdsEnablePageReadProgress, + opdsItemsPerPage = opdsItemsPerPage, + opdsMarkAsReadOnDownload = opdsMarkAsReadOnDownload, + opdsShowOnlyDownloadedChapters = opdsShowOnlyDownloadedChapters, + opdsShowOnlyUnreadChapters = opdsShowOnlyUnreadChapters, + opdsUseBinaryFileSizes = opdsUseBinaryFileSizes, port = port, socksProxyEnabled = socksProxyEnabled, socksProxyHost = socksProxyHost, @@ -75,6 +115,57 @@ class SettingsRepositoryImpl( webUIUpdateCheckInterval = webUIUpdateCheckInterval, ) + + private fun AuthMode.toDomain() = + when (this) { + AuthMode.NONE -> DomainAuthMode.NONE + AuthMode.BASIC_AUTH -> DomainAuthMode.BASIC_AUTH + AuthMode.SIMPLE_LOGIN -> DomainAuthMode.SIMPLE_LOGIN + AuthMode.UI_LOGIN -> DomainAuthMode.UI_LOGIN + AuthMode.UNKNOWN__ -> DomainAuthMode.UNKNOWN__ + } + + private fun DatabaseType.toDomain() = + when (this) { + DatabaseType.H2 -> DomainDatabaseType.H2 + DatabaseType.POSTGRESQL -> DomainDatabaseType.POSTGRESQL + DatabaseType.UNKNOWN__ -> DomainDatabaseType.UNKNOWN__ + } + + private fun DownloadConversion.toDomain() = + DomainDownloadConversion( + compressionLevel = compressionLevel, + mimeType = mimeType, + target = target, + ) + + private fun KoreaderSyncConflictStrategy.toDomain() = + when (this) { + KoreaderSyncConflictStrategy.PROMPT -> DomainKoreaderSyncConflictStrategy.PROMPT + KoreaderSyncConflictStrategy.KEEP_LOCAL -> DomainKoreaderSyncConflictStrategy.KEEP_LOCAL + KoreaderSyncConflictStrategy.KEEP_REMOTE -> DomainKoreaderSyncConflictStrategy.KEEP_REMOTE + KoreaderSyncConflictStrategy.DISABLED -> DomainKoreaderSyncConflictStrategy.DISABLED + KoreaderSyncConflictStrategy.UNKNOWN__ -> DomainKoreaderSyncConflictStrategy.UNKNOWN__ + } + + private fun KoreaderSyncChecksumMethod.toDomain() = + when (this) { + KoreaderSyncChecksumMethod.BINARY -> DomainKoreaderSyncChecksumMethod.BINARY + KoreaderSyncChecksumMethod.FILENAME -> DomainKoreaderSyncChecksumMethod.FILENAME + KoreaderSyncChecksumMethod.UNKNOWN__ -> DomainKoreaderSyncChecksumMethod.UNKNOWN__ + } + + private fun SortOrder.toDomain() = + when (this) { + SortOrder.ASC -> DomainSortOrder.ASC + SortOrder.DESC -> DomainSortOrder.DESC + SortOrder.ASC_NULLS_FIRST -> DomainSortOrder.ASC_NULLS_FIRST + SortOrder.DESC_NULLS_FIRST -> DomainSortOrder.DESC_NULLS_FIRST + SortOrder.ASC_NULLS_LAST -> DomainSortOrder.ASC_NULLS_LAST + SortOrder.DESC_NULLS_LAST -> DomainSortOrder.DESC_NULLS_LAST + SortOrder.UNKNOWN__ -> DomainSortOrder.UNKNOWN__ + } + private fun WebUIChannel.toDomain() = when (this) { WebUIChannel.BUNDLED -> DomainWebUIChannel.BUNDLED @@ -98,6 +189,56 @@ class SettingsRepositoryImpl( WebUIInterface.UNKNOWN__ -> DomainWebUIInterface.UNKNOWN__ } + private fun DomainAuthMode.toGraphQL() = + when (this) { + DomainAuthMode.NONE -> AuthMode.NONE + DomainAuthMode.BASIC_AUTH -> AuthMode.BASIC_AUTH + DomainAuthMode.SIMPLE_LOGIN -> AuthMode.SIMPLE_LOGIN + DomainAuthMode.UI_LOGIN -> AuthMode.UI_LOGIN + DomainAuthMode.UNKNOWN__ -> AuthMode.UNKNOWN__ + } + + private fun DomainDatabaseType.toGraphQL() = + when (this) { + DomainDatabaseType.H2 -> DatabaseType.H2 + DomainDatabaseType.POSTGRESQL -> DatabaseType.POSTGRESQL + DomainDatabaseType.UNKNOWN__ -> DatabaseType.UNKNOWN__ + } + + private fun DomainDownloadConversion.toGraphQL() = + SettingsDownloadConversionTypeInput( + compressionLevel = compressionLevel.toOptional(), + mimeType = mimeType, + target = target, + ) + + private fun DomainKoreaderSyncChecksumMethod.toGraphQL() = + when (this) { + DomainKoreaderSyncChecksumMethod.BINARY -> KoreaderSyncChecksumMethod.BINARY + DomainKoreaderSyncChecksumMethod.FILENAME -> KoreaderSyncChecksumMethod.FILENAME + DomainKoreaderSyncChecksumMethod.UNKNOWN__ -> KoreaderSyncChecksumMethod.UNKNOWN__ + } + + private fun DomainKoreaderSyncConflictStrategy.toGraphQL() = + when (this) { + DomainKoreaderSyncConflictStrategy.PROMPT -> KoreaderSyncConflictStrategy.PROMPT + DomainKoreaderSyncConflictStrategy.KEEP_LOCAL -> KoreaderSyncConflictStrategy.KEEP_LOCAL + DomainKoreaderSyncConflictStrategy.KEEP_REMOTE -> KoreaderSyncConflictStrategy.KEEP_REMOTE + DomainKoreaderSyncConflictStrategy.DISABLED -> KoreaderSyncConflictStrategy.DISABLED + DomainKoreaderSyncConflictStrategy.UNKNOWN__ -> KoreaderSyncConflictStrategy.UNKNOWN__ + } + + private fun DomainSortOrder.toGraphQL() = + when (this) { + DomainSortOrder.ASC -> SortOrder.ASC + DomainSortOrder.DESC -> SortOrder.DESC + DomainSortOrder.ASC_NULLS_FIRST -> SortOrder.ASC_NULLS_FIRST + DomainSortOrder.DESC_NULLS_FIRST -> SortOrder.DESC_NULLS_FIRST + DomainSortOrder.ASC_NULLS_LAST -> SortOrder.ASC_NULLS_LAST + DomainSortOrder.DESC_NULLS_LAST -> SortOrder.DESC_NULLS_LAST + DomainSortOrder.UNKNOWN__ -> SortOrder.UNKNOWN__ + } + private fun DomainWebUIChannel.toGraphQL() = when (this) { DomainWebUIChannel.BUNDLED -> WebUIChannel.BUNDLED @@ -130,17 +271,23 @@ class SettingsRepositoryImpl( private fun SetSettingsInput.toMutation() = SetSettingsMutation( + authMode = authMode?.toGraphQL().toOptional(), + authPassword = authPassword.toOptional(), + authUsername = authUsername.toOptional(), + autoDownloadIgnoreReUploads = autoDownloadIgnoreReUploads.toOptional(), autoDownloadNewChapters = autoDownloadNewChapters.toOptional(), autoDownloadNewChaptersLimit = autoDownloadNewChaptersLimit.toOptional(), backupInterval = backupInterval.toOptional(), backupPath = backupPath.toOptional(), backupTTL = backupTTL.toOptional(), backupTime = backupTime.toOptional(), - basicAuthEnabled = basicAuthEnabled.toOptional(), - basicAuthPassword = basicAuthPassword.toOptional(), - basicAuthUsername = basicAuthUsername.toOptional(), + databasePassword = databasePassword.toOptional(), + databaseType = databaseType?.toGraphQL().toOptional(), + databaseUrl = databaseUrl.toOptional(), + databaseUsername = databaseUsername.toOptional(), debugLogsEnabled = debugLogsEnabled.toOptional(), downloadAsCbz = downloadAsCbz.toOptional(), + downloadConversions = downloadConversions?.map { it.toGraphQL() }.toOptional(), downloadsPath = downloadsPath.toOptional(), electronPath = electronPath.toOptional(), excludeCompleted = excludeCompleted.toOptional(), @@ -148,17 +295,38 @@ class SettingsRepositoryImpl( excludeNotStarted = excludeNotStarted.toOptional(), excludeUnreadChapters = excludeUnreadChapters.toOptional(), extensionRepos = extensionRepos.toOptional(), + flareSolverrAsResponseFallback = flareSolverrAsResponseFallback.toOptional(), flareSolverrEnabled = flareSolverrEnabled.toOptional(), flareSolverrSessionName = flareSolverrSessionName.toOptional(), flareSolverrSessionTtl = flareSolverrSessionTtl.toOptional(), flareSolverrTimeout = flareSolverrTimeout.toOptional(), flareSolverrUrl = flareSolverrUrl.toOptional(), globalUpdateInterval = globalUpdateInterval.toOptional(), - gqlDebugLogsEnabled = gqlDebugLogsEnabled.toOptional(), initialOpenInBrowserEnabled = initialOpenInBrowserEnabled.toOptional(), ip = ip.toOptional(), + jwtAudience = jwtAudience.toOptional(), + jwtRefreshExpiry = jwtRefreshExpiry.toOptional(), + jwtTokenExpiry = jwtTokenExpiry.toOptional(), + koreaderSyncChecksumMethod = koreaderSyncChecksumMethod?.toGraphQL().toOptional(), + koreaderSyncDeviceId = koreaderSyncDeviceId.toOptional(), + koreaderSyncPercentageTolerance = koreaderSyncPercentageTolerance.toOptional(), + koreaderSyncServerUrl = koreaderSyncServerUrl.toOptional(), + koreaderSyncStrategyBackward = koreaderSyncStrategyBackward?.toGraphQL().toOptional(), + koreaderSyncStrategyForward = koreaderSyncStrategyForward?.toGraphQL().toOptional(), + koreaderSyncUserkey = koreaderSyncUserkey.toOptional(), + koreaderSyncUsername = koreaderSyncUsername.toOptional(), localSourcePath = localSourcePath.toOptional(), + maxLogFileSize = maxLogFileSize.toOptional(), + maxLogFiles = maxLogFiles.toOptional(), + maxLogFolderSize = maxLogFolderSize.toOptional(), maxSourcesInParallel = maxSourcesInParallel.toOptional(), + opdsChapterSortOrder = opdsChapterSortOrder?.toGraphQL().toOptional(), + opdsEnablePageReadProgress = opdsEnablePageReadProgress.toOptional(), + opdsItemsPerPage = opdsItemsPerPage.toOptional(), + opdsMarkAsReadOnDownload = opdsMarkAsReadOnDownload.toOptional(), + opdsShowOnlyDownloadedChapters = opdsShowOnlyDownloadedChapters.toOptional(), + opdsShowOnlyUnreadChapters = opdsShowOnlyUnreadChapters.toOptional(), + opdsUseBinaryFileSizes = opdsUseBinaryFileSizes.toOptional(), port = port.toOptional(), socksProxyEnabled = socksProxyEnabled.toOptional(), socksProxyHost = socksProxyHost.toOptional(), diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/source/SourceRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/source/SourceRepositoryImpl.kt new file mode 100644 index 00000000..e79d666b --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/source/SourceRepositoryImpl.kt @@ -0,0 +1,452 @@ +package ca.gosyer.jui.data.source + +import ca.gosyer.jui.data.graphql.FetchLatestMangaMutation +import ca.gosyer.jui.data.graphql.FetchPopularMangaMutation +import ca.gosyer.jui.data.graphql.FetchSearchMangaMutation +import ca.gosyer.jui.data.graphql.GetSourceFiltersQuery +import ca.gosyer.jui.data.graphql.GetSourceListQuery +import ca.gosyer.jui.data.graphql.GetSourcePreferencesQuery +import ca.gosyer.jui.data.graphql.GetSourceQuery +import ca.gosyer.jui.data.graphql.SetSourceSettingMutation +import ca.gosyer.jui.data.graphql.fragment.SourceFragment +import ca.gosyer.jui.data.graphql.type.FilterChangeInput +import ca.gosyer.jui.data.graphql.type.SortSelectionInput +import ca.gosyer.jui.data.graphql.type.SourcePreferenceChangeInput +import ca.gosyer.jui.data.graphql.type.TriState +import ca.gosyer.jui.data.manga.MangaRepositoryImpl.Companion.toManga +import ca.gosyer.jui.data.util.toOptional +import ca.gosyer.jui.domain.server.Http +import ca.gosyer.jui.domain.source.model.MangaPage +import ca.gosyer.jui.domain.source.model.Source +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter.TriState.TriStateValue +import ca.gosyer.jui.domain.source.model.sourcepreference.CheckBoxSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.EditTextSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.ListSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.MultiSelectListSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.SwitchSourcePreference +import ca.gosyer.jui.domain.source.service.SourceRepository +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class SourceRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +) : SourceRepository { + override fun getSourceList(): Flow> { + return apolloClient.query( + GetSourceListQuery() + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.sources.nodes.map { it.sourceFragment.toSource() } + } + } + + override fun getSourceInfo(sourceId: Long): Flow { + return apolloClient.query( + GetSourceQuery(sourceId) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.source.sourceFragment.toSource() + } + } + + override fun getPopularManga( + sourceId: Long, + pageNum: Int, + ): Flow { + return apolloClient.mutation( + FetchPopularMangaMutation(sourceId, pageNum) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + MangaPage( + data.fetchSourceManga!!.mangas.map { it.mangaFragment.toManga() }, + data.fetchSourceManga.hasNextPage + ) + } + } + + override fun getLatestManga( + sourceId: Long, + pageNum: Int, + ): Flow { + return apolloClient.mutation( + FetchLatestMangaMutation(sourceId, pageNum) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + MangaPage( + data.fetchSourceManga!!.mangas.map { it.mangaFragment.toManga() }, + data.fetchSourceManga.hasNextPage + ) + } + } + + override fun getFilterList( + sourceId: Long, + ): Flow> { + return apolloClient.query( + GetSourceFiltersQuery(sourceId) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.source.filters.mapIndexed { index, filter -> + when (filter.__typename) { + "CheckBoxFilter" -> { + val filter = filter.onCheckBoxFilter!! + SourceFilter.Checkbox( + index, + filter.name, + filter.checkBoxFilterDefault, + ) + } + "HeaderFilter" -> { + val filter = filter.onHeaderFilter!! + SourceFilter.Header( + index, + filter.name, + ) + } + "SelectFilter" -> { + val filter = filter.onSelectFilter!! + SourceFilter.Select( + index, + filter.name, + filter.values, + filter.selectFilterDefault + ) + } + "TriStateFilter" -> { + val filter = filter.onTriStateFilter!! + SourceFilter.TriState( + index, + filter.name, + when (filter.triStateFilterDefault) { + TriState.IGNORE -> TriStateValue.IGNORE + TriState.INCLUDE -> TriStateValue.INCLUDE + TriState.EXCLUDE -> TriStateValue.EXCLUDE + TriState.UNKNOWN__ -> TriStateValue.IGNORE + }, + ) + } + "TextFilter" -> { + val filter = filter.onTextFilter!! + SourceFilter.Text( + index, + filter.name, + filter.textFilterDefault, + ) + } + "SortFilter" -> { + val filter = filter.onSortFilter!! + SourceFilter.Sort( + index, + filter.name, + filter.values, + filter.sortFilterDefault?.let { + SourceFilter.Sort.SelectionChange(it.ascending, it.index) + }, + ) + } + "SeparatorFilter" -> { + val filter = filter.onSeparatorFilter!! + SourceFilter.Separator( + index, + filter.name, + ) + } + "GroupFilter" -> { + SourceFilter.Group( + index, + filter.onGroupFilter!!.name, + filter.onGroupFilter.filters.mapIndexed { index, filter -> + when (filter.__typename) { + "CheckBoxFilter" -> { + val filter = filter.onCheckBoxFilter!! + SourceFilter.Checkbox( + index, + filter.name, + filter.checkBoxFilterDefault, + ) + } + "HeaderFilter" -> { + val filter = filter.onHeaderFilter!! + SourceFilter.Header( + index, + filter.name, + ) + } + "SelectFilter" -> { + val filter = filter.onSelectFilter!! + SourceFilter.Select( + index, + filter.name, + filter.values, + filter.selectFilterDefault + ) + } + "TriStateFilter" -> { + val filter = filter.onTriStateFilter!! + SourceFilter.TriState( + index, + filter.name, + when (filter.triStateFilterDefault) { + TriState.IGNORE -> TriStateValue.IGNORE + TriState.INCLUDE -> TriStateValue.INCLUDE + TriState.EXCLUDE -> TriStateValue.EXCLUDE + TriState.UNKNOWN__ -> TriStateValue.IGNORE + }, + ) + } + "TextFilter" -> { + val filter = filter.onTextFilter!! + SourceFilter.Text( + index, + filter.name, + filter.textFilterDefault, + ) + } + "SortFilter" -> { + val filter = filter.onSortFilter!! + SourceFilter.Sort( + index, + filter.name, + filter.values, + filter.sortFilterDefault?.let { + SourceFilter.Sort.SelectionChange(it.ascending, it.index) + }, + ) + } + "SeparatorFilter" -> { + val filter = filter.onSeparatorFilter!! + SourceFilter.Separator( + index, + filter.name, + ) + } + else -> SourceFilter.Header(index, "") + } + } + ) + } + else -> SourceFilter.Header(index, "") + } + } + } + } + + fun SourceFilter.toFilterChange(): List? { + return when (this) { + is SourceFilter.Checkbox -> if (value != default) { + listOf(FilterChangeInput(position = position, checkBoxState = value.toOptional())) + } else { + null + } + is SourceFilter.Header -> null + is SourceFilter.Select -> if (value != default) { + listOf(FilterChangeInput(position = position, selectState = value.toOptional())) + } else { + null + } + is SourceFilter.Separator -> null + is SourceFilter.Sort -> if (value != default) { + listOf( + FilterChangeInput( + position = position, + sortState = value?.let { SortSelectionInput(it.ascending, it.index) }.toOptional() + ) + ) + } else { + null + } + is SourceFilter.Text -> if (value != default) { + listOf(FilterChangeInput(position = position, textState = value.toOptional())) + } else { + null + } + is SourceFilter.TriState -> if (value != default) { + listOf( + FilterChangeInput( + position = position, + triState = when (value) { + TriStateValue.IGNORE -> TriState.IGNORE + TriStateValue.INCLUDE -> TriState.INCLUDE + TriStateValue.EXCLUDE -> TriState.EXCLUDE + }.toOptional(), + ) + ) + } else { + null + } + is SourceFilter.Group -> value.mapNotNull { + FilterChangeInput( + position = position, + groupChange = it.toFilterChange() + ?.firstOrNull() + ?.toOptional() + ?: return@mapNotNull null + ) + } + } + } + override fun getSearchResults( + sourceId: Long, + pageNum: Int, + searchTerm: String?, + filters: List?, + ): Flow { + return apolloClient.mutation( + FetchSearchMangaMutation( + sourceId, + pageNum, + searchTerm.toOptional(), + filters?.mapNotNull { + it.toFilterChange() + }?.flatten().toOptional(), + ), + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + MangaPage( + data.fetchSourceManga!!.mangas.map { it.mangaFragment.toManga() }, + data.fetchSourceManga.hasNextPage + ) + } + } + + override fun getSourceSettings(sourceId: Long): Flow> { + return apolloClient.query( + GetSourcePreferencesQuery(sourceId) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.source.preferences.mapIndexedNotNull { index, preference -> + println("Test " + preference.__typename + " $index") + when (preference.__typename) { + "CheckBoxPreference" -> preference.onCheckBoxPreference!!.let { + CheckBoxSourcePreference( + position = index, + key = it.key, + title = it.checkBoxTitle, + summary = it.summary, + visible = it.visible, + enabled = it.enabled, + currentValue = it.checkBoxCheckBoxCurrentValue, + default = it.checkBoxDefault, + ) + } + "EditTextPreference" -> preference.onEditTextPreference!!.let { + EditTextSourcePreference( + position = index, + key = it.key, + title = it.editTextPreferenceTitle, + summary = it.summary, + visible = it.visible, + enabled = it.enabled, + currentValue = it.editTextPreferenceCurrentValue, + default = it.editTextPreferenceDefault, + dialogTitle = it.dialogTitle, + dialogMessage = it.dialogMessage, + text = it.text, + ) + } + "SwitchPreference" -> preference.onSwitchPreference!!.let { + SwitchSourcePreference( + position = index, + key = it.key, + title = it.switchPreferenceTitle, + summary = it.summary, + visible = it.visible, + enabled = it.enabled, + currentValue = it.switchPreferenceCurrentValue, + default = it.switchPreferenceDefault, + ) + } + "MultiSelectListPreference" -> preference.onMultiSelectListPreference!!.let { + MultiSelectListSourcePreference( + position = index, + key = it.key, + title = it.multiSelectListPreferenceTitle, + summary = it.summary, + visible = it.visible, + enabled = it.enabled, + currentValue = it.multiSelectListPreferenceCurrentValue, + default = it.multiSelectListPreferenceDefault, + dialogTitle = it.dialogTitle, + dialogMessage = it.dialogMessage, + entries = it.entries, + entryValues = it.entryValues + ) + } + "ListPreference" -> preference.onListPreference!!.let { + ListSourcePreference( + position = index, + key = it.key, + title = it.listPreferenceTitle, + summary = it.summary, + visible = it.visible, + enabled = it.enabled, + currentValue = it.listPreferenceCurrentValue, + default = it.listPreferenceDefault, + entries = it.entries, + entryValues = it.entryValues + ) + } + else -> null + } + } + } + } + + override fun setSourceSetting( + sourceId: Long, + sourcePreference: SourcePreference, + ): Flow { + return apolloClient.mutation( + SetSourceSettingMutation( + sourceId, + SourcePreferenceChangeInput( + position = sourcePreference.position, + checkBoxState = (sourcePreference as? CheckBoxSourcePreference)?.currentValue.toOptional(), + switchState = (sourcePreference as? SwitchSourcePreference)?.currentValue.toOptional(), + editTextState = (sourcePreference as? EditTextSourcePreference)?.currentValue.toOptional(), + multiSelectState = (sourcePreference as? MultiSelectListSourcePreference)?.currentValue.toOptional(), + listState = (sourcePreference as? ListSourcePreference)?.currentValue.toOptional(), + ) + ) + ) + .toFlow() + .map { + it.dataAssertNoErrors.updateSourcePreference!!.clientMutationId + } + } + + companion object { + internal fun SourceFragment.toSource(): Source { + return Source( + id, + name, + lang, + iconUrl, + supportsLatest, + isConfigurable, + isNsfw, + displayName + ) + } + } +} diff --git a/data/src/commonMain/kotlin/ca/gosyer/jui/data/updates/UpdatesRepositoryImpl.kt b/data/src/commonMain/kotlin/ca/gosyer/jui/data/updates/UpdatesRepositoryImpl.kt new file mode 100644 index 00000000..4e2a3afd --- /dev/null +++ b/data/src/commonMain/kotlin/ca/gosyer/jui/data/updates/UpdatesRepositoryImpl.kt @@ -0,0 +1,63 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.data.updates + +import ca.gosyer.jui.data.chapter.ChapterRepositoryImpl.Companion.toMangaAndChapter +import ca.gosyer.jui.data.graphql.GetChapterUpdatesQuery +import ca.gosyer.jui.data.graphql.UpdateCategoryMutation +import ca.gosyer.jui.data.graphql.UpdateLibraryMutation +import ca.gosyer.jui.domain.server.Http +import ca.gosyer.jui.domain.updates.model.Updates +import ca.gosyer.jui.domain.updates.service.UpdatesRepository +import com.apollographql.apollo.ApolloClient +import io.ktor.http.Url +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class UpdatesRepositoryImpl( + private val apolloClient: ApolloClient, + private val http: Http, + private val serverUrl: Url, +): UpdatesRepository { + override fun getRecentUpdates(pageNum: Int): Flow { + return apolloClient.query( + GetChapterUpdatesQuery(50, pageNum * 50) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + Updates( + data.chapters.nodes.map { it.chapterWithMangaFragment.toMangaAndChapter() }, + data.chapters.pageInfo.hasNextPage + ) + + } + } + + override fun updateLibrary(): Flow { + return apolloClient.mutation( + UpdateLibraryMutation() + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateLibraryManga!!.clientMutationId + } + } + + override fun updateCategory(categoryId: Long): Flow { + return apolloClient.mutation( + UpdateCategoryMutation(listOf(categoryId.toInt())) + ) + .toFlow() + .map { + val data = it.dataAssertNoErrors + data.updateCategoryManga!!.clientMutationId + } + } + +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/backup/model/RestoreStatus.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/backup/model/RestoreStatus.kt index 7590627e..ce63c8f9 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/backup/model/RestoreStatus.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/backup/model/RestoreStatus.kt @@ -14,6 +14,8 @@ enum class RestoreState { FAILURE, RESTORING_CATEGORIES, RESTORING_MANGA, + RESTORING_META, + RESTORING_SETTINGS, UNKNOWN, } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/AddMangaToCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/AddMangaToCategory.kt index ff1903e0..78bd23bc 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/AddMangaToCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/AddMangaToCategory.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.manga.model.Manga import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect @@ -20,7 +20,7 @@ import org.lighthousegames.logging.logging class AddMangaToCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -49,7 +49,7 @@ class AddMangaToCategory mangaId: Long, categoryId: Long, ) = if (categoryId != 0L) { - categoryRepositoryOld.addMangaToCategory(mangaId, categoryId) + categoryRepository.addMangaToCategory(mangaId, categoryId) .map { serverListeners.updateCategoryManga(categoryId) } } else { flow { @@ -62,7 +62,7 @@ class AddMangaToCategory manga: Manga, category: Category, ) = if (category.id != 0L) { - categoryRepositoryOld.addMangaToCategory(manga.id, category.id) + categoryRepository.addMangaToCategory(manga.id, category.id) .map { serverListeners.updateCategoryManga(category.id) } } else { flow { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/CreateCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/CreateCategory.kt index 66d49fb6..80c36d7a 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/CreateCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/CreateCategory.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.category.interactor -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class CreateCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( name: String, @@ -27,7 +27,7 @@ class CreateCategory } .collect() - fun asFlow(name: String) = categoryRepositoryOld.createCategory(name) + fun asFlow(name: String) = categoryRepository.createCategory(name) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/DeleteCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/DeleteCategory.kt index faefe450..0704cc75 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/DeleteCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/DeleteCategory.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class DeleteCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( categoryId: Long, @@ -38,9 +38,9 @@ class DeleteCategory } .collect() - fun asFlow(categoryId: Long) = categoryRepositoryOld.deleteCategory(categoryId) + fun asFlow(categoryId: Long) = categoryRepository.deleteCategory(categoryId) - fun asFlow(category: Category) = categoryRepositoryOld.deleteCategory(category.id) + fun asFlow(category: Category) = categoryRepository.deleteCategory(category.id) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetCategories.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetCategories.kt index 822ca8b1..7841d13a 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetCategories.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetCategories.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.category.interactor -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.singleOrNull @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class GetCategories @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( dropDefault: Boolean = false, @@ -29,7 +29,7 @@ class GetCategories .singleOrNull() fun asFlow(dropDefault: Boolean = false) = - categoryRepositoryOld.getCategories() + categoryRepository.getCategories() .map { categories -> if (dropDefault) { categories.filterNot { it.name.equals("default", true) } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaCategories.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaCategories.kt index 3a873764..1ed44476 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaCategories.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaCategories.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.category.interactor -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.manga.model.Manga import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class GetMangaCategories @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( mangaId: Long, @@ -38,9 +38,9 @@ class GetMangaCategories } .singleOrNull() - fun asFlow(mangaId: Long) = categoryRepositoryOld.getMangaCategories(mangaId) + fun asFlow(mangaId: Long) = categoryRepository.getMangaCategories(mangaId) - fun asFlow(manga: Manga) = categoryRepositoryOld.getMangaCategories(manga.id) + fun asFlow(manga: Manga) = categoryRepository.getMangaCategories(manga.id) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaListFromCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaListFromCategory.kt index 04e80a2f..2f8d0559 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaListFromCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/GetMangaListFromCategory.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.take @@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging class GetMangaListFromCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -45,12 +45,12 @@ class GetMangaListFromCategory fun asFlow(categoryId: Long) = serverListeners.combineCategoryManga( - categoryRepositoryOld.getMangaFromCategory(categoryId), + categoryRepository.getMangaFromCategory(categoryId), ) { categoryId == it } fun asFlow(category: Category) = serverListeners.combineCategoryManga( - categoryRepositoryOld.getMangaFromCategory(category.id), + categoryRepository.getMangaFromCategory(category.id), ) { category.id == it } companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ModifyCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ModifyCategory.kt index 6c574efa..7864b9c1 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ModifyCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ModifyCategory.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class ModifyCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( categoryId: Long, @@ -45,7 +45,7 @@ class ModifyCategory fun asFlow( categoryId: Long, name: String, - ) = categoryRepositoryOld.modifyCategory( + ) = categoryRepository.modifyCategory( categoryId = categoryId, name = name, ) @@ -53,7 +53,7 @@ class ModifyCategory fun asFlow( category: Category, name: String? = null, - ) = categoryRepositoryOld.modifyCategory( + ) = categoryRepository.modifyCategory( categoryId = category.id, name = name ?: category.name, ) diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/RemoveMangaFromCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/RemoveMangaFromCategory.kt index b4a32b9b..e69fa7b0 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/RemoveMangaFromCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/RemoveMangaFromCategory.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import ca.gosyer.jui.domain.manga.model.Manga import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect @@ -20,7 +20,7 @@ import org.lighthousegames.logging.logging class RemoveMangaFromCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -49,7 +49,7 @@ class RemoveMangaFromCategory mangaId: Long, categoryId: Long, ) = if (categoryId != 0L) { - categoryRepositoryOld.removeMangaFromCategory(mangaId, categoryId) + categoryRepository.removeMangaFromCategory(mangaId, categoryId) .map { serverListeners.updateCategoryManga(categoryId) } } else { flow { @@ -62,7 +62,7 @@ class RemoveMangaFromCategory manga: Manga, category: Category, ) = if (category.id != 0L) { - categoryRepositoryOld.removeMangaFromCategory(manga.id, category.id) + categoryRepository.removeMangaFromCategory(manga.id, category.id) .map { serverListeners.updateCategoryManga(category.id) } } else { flow { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ReorderCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ReorderCategory.kt index 617b60eb..db3b1bc6 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ReorderCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/ReorderCategory.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.category.interactor -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,23 +15,23 @@ import org.lighthousegames.logging.logging class ReorderCategory @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( - to: Int, - from: Int, + categoryId: Long, + position: Int, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(to, from) + ) = asFlow(categoryId, position) .catch { onError(it) - log.warn(it) { "Failed to move category from $from to $to" } + log.warn(it) { "Failed to move category $categoryId to $position" } } .collect() fun asFlow( - to: Int, - from: Int, - ) = categoryRepositoryOld.reorderCategory(to, from) + categoryId: Long, + position: Int, + ) = categoryRepository.reorderCategory(categoryId, position) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt index d38833a6..21f91de9 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/interactor/UpdateCategoryMeta.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.category.interactor import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.category.service.CategoryRepositoryOld +import ca.gosyer.jui.domain.category.service.CategoryRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow @@ -17,7 +17,7 @@ import org.lighthousegames.logging.logging class UpdateCategoryMeta @Inject constructor( - private val categoryRepositoryOld: CategoryRepositoryOld, + private val categoryRepository: CategoryRepository, ) { suspend fun await( category: Category, @@ -35,7 +35,7 @@ class UpdateCategoryMeta example: Int = category.meta.example, ) = flow { if (example != category.meta.example) { - categoryRepositoryOld.updateCategoryMeta( + categoryRepository.updateCategoryMeta( category.id, "example", example.toString(), diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt new file mode 100644 index 00000000..d845b71d --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/category/service/CategoryRepository.kt @@ -0,0 +1,58 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.category.service + +import ca.gosyer.jui.domain.category.model.Category +import ca.gosyer.jui.domain.manga.model.Manga +import kotlinx.coroutines.flow.Flow + +interface CategoryRepository { + + fun getMangaCategories( + mangaId: Long, + ): Flow> + + fun addMangaToCategory( + mangaId: Long, + categoryId: Long, + ): Flow + + fun removeMangaFromCategory( + mangaId: Long, + categoryId: Long, + ): Flow + + fun getCategories(): Flow> + + fun createCategory( + name: String, + ): Flow + + fun modifyCategory( + categoryId: Long, + name: String, + ): Flow + + fun reorderCategory( + categoryId: Long, + position: Int, + ): Flow + + fun deleteCategory( + categoryId: Long, + ): Flow + + fun getMangaFromCategory( + categoryId: Long, + ): Flow> + + fun updateCategoryMeta( + categoryId: Long, + key: String, + value: String, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/BatchChapterDownload.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/BatchChapterDownload.kt index 7f4b3424..6ebbfe41 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/BatchChapterDownload.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/BatchChapterDownload.kt @@ -6,8 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.download.model.DownloadEnqueue -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +15,7 @@ import org.lighthousegames.logging.logging class BatchChapterDownload @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await( chapterIds: List, @@ -38,9 +37,9 @@ class BatchChapterDownload } .collect() - fun asFlow(chapterIds: List) = downloadRepositoryOld.batchDownload(DownloadEnqueue(chapterIds)) + fun asFlow(chapterIds: List) = downloadRepository.batchDownload(chapterIds) - fun asFlow(vararg chapterIds: Long) = downloadRepositoryOld.batchDownload(DownloadEnqueue(chapterIds.asList())) + fun asFlow(vararg chapterIds: Long) = downloadRepository.batchDownload(chapterIds.asList()) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ClearDownloadQueue.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ClearDownloadQueue.kt index e2f4fdd5..28352181 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ClearDownloadQueue.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ClearDownloadQueue.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class ClearDownloadQueue @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class ClearDownloadQueue } .collect() - fun asFlow() = downloadRepositoryOld.clearDownloadQueue() + fun asFlow() = downloadRepository.clearDownloadQueue() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/QueueChapterDownload.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/QueueChapterDownload.kt index 945401bb..11b4e56c 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/QueueChapterDownload.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/QueueChapterDownload.kt @@ -6,9 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.chapter.model.Chapter -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld -import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -17,51 +15,21 @@ import org.lighthousegames.logging.logging class QueueChapterDownload @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await( - mangaId: Long, - index: Int, + chapterId: Long, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(mangaId, index) + ) = asFlow(chapterId) .catch { onError(it) - log.warn(it) { "Failed to queue chapter $index of $mangaId for a download" } - } - .collect() - - suspend fun await( - manga: Manga, - index: Int, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(manga, index) - .catch { - onError(it) - log.warn(it) { "Failed to queue chapter $index of ${manga.title}(${manga.id}) for a download" } - } - .collect() - - suspend fun await( - chapter: Chapter, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(chapter) - .catch { - onError(it) - log.warn(it) { "Failed to queue chapter ${chapter.index} of ${chapter.mangaId} for a download" } + log.warn(it) { "Failed to queue chapter ${chapterId} for a download" } } .collect() fun asFlow( - mangaId: Long, - index: Int, - ) = downloadRepositoryOld.queueChapterDownload(mangaId, index) - - fun asFlow( - manga: Manga, - index: Int, - ) = downloadRepositoryOld.queueChapterDownload(manga.id, index) - - fun asFlow(chapter: Chapter) = downloadRepositoryOld.queueChapterDownload(chapter.mangaId, chapter.index) + chapterId: Long, + ) = downloadRepository.queueChapterDownload(chapterId) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt index 38a20362..eba80cd1 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/ReorderChapterDownload.kt @@ -6,9 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.chapter.model.Chapter -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld -import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -17,59 +15,23 @@ import org.lighthousegames.logging.logging class ReorderChapterDownload @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await( - mangaId: Long, - index: Int, + chapterId: Long, to: Int, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(mangaId, index, to) + ) = asFlow(chapterId, to) .catch { onError(it) - log.warn(it) { "Failed to reorder chapter download for $index of $mangaId to $to" } - } - .collect() - - suspend fun await( - manga: Manga, - index: Int, - to: Int, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(manga, index, to) - .catch { - onError(it) - log.warn(it) { "Failed to reorder chapter download for $index of ${manga.title}(${manga.id}) to $to" } - } - .collect() - - suspend fun await( - chapter: Chapter, - to: Int, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(chapter, to) - .catch { - onError(it) - log.warn(it) { "Failed to reorder chapter download for ${chapter.index} of ${chapter.mangaId} to $to" } + log.warn(it) { "Failed to reorder chapter download for $chapterId to $to" } } .collect() fun asFlow( - mangaId: Long, - index: Int, + chapterId: Long, to: Int, - ) = downloadRepositoryOld.reorderChapterDownload(mangaId, index, to) - - fun asFlow( - manga: Manga, - index: Int, - to: Int, - ) = downloadRepositoryOld.reorderChapterDownload(manga.id, index, to) - - fun asFlow( - chapter: Chapter, - to: Int, - ) = downloadRepositoryOld.reorderChapterDownload(chapter.mangaId, chapter.index, to) + ) = downloadRepository.reorderChapterDownload(chapterId, to) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StartDownloading.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StartDownloading.kt index a0727bbd..c15e3a3f 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StartDownloading.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StartDownloading.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class StartDownloading @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class StartDownloading } .collect() - fun asFlow() = downloadRepositoryOld.startDownloading() + fun asFlow() = downloadRepository.startDownloading() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopChapterDownload.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopChapterDownload.kt index e12a390e..22ca2515 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopChapterDownload.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopChapterDownload.kt @@ -6,9 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.chapter.model.Chapter -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld -import ca.gosyer.jui.domain.manga.model.Manga +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -17,51 +15,19 @@ import org.lighthousegames.logging.logging class StopChapterDownload @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await( - mangaId: Long, - index: Int, + chapterId: Long, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(mangaId, index) + ) = asFlow(chapterId) .catch { onError(it) - log.warn(it) { "Failed to stop chapter download for $index of $mangaId" } + log.warn(it) { "Failed to stop chapter download for $chapterId" } } .collect() - suspend fun await( - manga: Manga, - index: Int, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(manga, index) - .catch { - onError(it) - log.warn(it) { "Failed to stop chapter download for $index of ${manga.title}(${manga.id})" } - } - .collect() - - suspend fun await( - chapter: Chapter, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(chapter) - .catch { - onError(it) - log.warn(it) { "Failed to stop chapter download for ${chapter.index} of ${chapter.mangaId}" } - } - .collect() - - fun asFlow( - mangaId: Long, - index: Int, - ) = downloadRepositoryOld.stopChapterDownload(mangaId, index) - - fun asFlow( - manga: Manga, - index: Int, - ) = downloadRepositoryOld.stopChapterDownload(manga.id, index) - - fun asFlow(chapter: Chapter) = downloadRepositoryOld.stopChapterDownload(chapter.mangaId, chapter.index) + fun asFlow(chapterId: Long) = downloadRepository.stopChapterDownload(chapterId) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopDownloading.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopDownloading.kt index 6c2ec9cd..a43e8769 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopDownloading.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/interactor/StopDownloading.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.download.interactor -import ca.gosyer.jui.domain.download.service.DownloadRepositoryOld +import ca.gosyer.jui.domain.download.service.DownloadRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class StopDownloading @Inject constructor( - private val downloadRepositoryOld: DownloadRepositoryOld, + private val downloadRepository: DownloadRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class StopDownloading } .collect() - fun asFlow() = downloadRepositoryOld.stopDownloading() + fun asFlow() = downloadRepository.stopDownloading() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt new file mode 100644 index 00000000..c8ef643d --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/download/service/DownloadRepository.kt @@ -0,0 +1,35 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.download.service + +import kotlinx.coroutines.flow.Flow + +interface DownloadRepository { + + fun startDownloading(): Flow + + fun stopDownloading(): Flow + + fun clearDownloadQueue(): Flow + + fun queueChapterDownload( + chapterId: Long, + ): Flow + + fun stopChapterDownload( + chapterId: Long, + ): Flow + + fun reorderChapterDownload( + chapterId: Long, + to: Int, + ): Flow + + fun batchDownload( + chapterIds: List, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/GetExtensionList.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/GetExtensionList.kt index cff914ad..c1f535eb 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/GetExtensionList.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/GetExtensionList.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.extension.interactor -import ca.gosyer.jui.domain.extension.service.ExtensionRepositoryOld +import ca.gosyer.jui.domain.extension.service.ExtensionRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class GetExtensionList @Inject constructor( - private val extensionRepositoryOld: ExtensionRepositoryOld, + private val extensionRepository: ExtensionRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class GetExtensionList } .singleOrNull() - fun asFlow() = extensionRepositoryOld.getExtensionList() + fun asFlow() = extensionRepository.getExtensionList() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtension.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtension.kt index 040ad994..cb157c18 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtension.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtension.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.extension.interactor import ca.gosyer.jui.domain.extension.model.Extension -import ca.gosyer.jui.domain.extension.service.ExtensionRepositoryOld +import ca.gosyer.jui.domain.extension.service.ExtensionRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class InstallExtension @Inject constructor( - private val extensionRepositoryOld: ExtensionRepositoryOld, + private val extensionRepository: ExtensionRepository, ) { suspend fun await( extension: Extension, @@ -28,7 +28,7 @@ class InstallExtension } .collect() - fun asFlow(extension: Extension) = extensionRepositoryOld.installExtension(extension.pkgName) + fun asFlow(extension: Extension) = extensionRepository.installExtension(extension.pkgName) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtensionFile.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtensionFile.kt index 67479148..f25bc0d6 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtensionFile.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/InstallExtensionFile.kt @@ -6,17 +6,19 @@ package ca.gosyer.jui.domain.extension.interactor -import ca.gosyer.jui.domain.extension.service.ExtensionRepositoryOld +import ca.gosyer.jui.domain.extension.service.ExtensionRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject +import okio.FileSystem import okio.Path +import okio.SYSTEM import org.lighthousegames.logging.logging class InstallExtensionFile @Inject constructor( - private val extensionRepositoryOld: ExtensionRepositoryOld, + private val extensionRepository: ExtensionRepository, ) { suspend fun await( path: Path, @@ -28,7 +30,7 @@ class InstallExtensionFile } .collect() - fun asFlow(path: Path) = extensionRepositoryOld.installExtension(ExtensionRepositoryOld.buildExtensionFormData(path)) + fun asFlow(path: Path) = extensionRepository.installExtension(FileSystem.SYSTEM.source(path)) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UninstallExtension.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UninstallExtension.kt index 0ab9346e..1a84e122 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UninstallExtension.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UninstallExtension.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.extension.interactor import ca.gosyer.jui.domain.extension.model.Extension -import ca.gosyer.jui.domain.extension.service.ExtensionRepositoryOld +import ca.gosyer.jui.domain.extension.service.ExtensionRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class UninstallExtension @Inject constructor( - private val extensionRepositoryOld: ExtensionRepositoryOld, + private val extensionRepository: ExtensionRepository, ) { suspend fun await( extension: Extension, @@ -28,7 +28,7 @@ class UninstallExtension } .collect() - fun asFlow(extension: Extension) = extensionRepositoryOld.uninstallExtension(extension.pkgName) + fun asFlow(extension: Extension) = extensionRepository.uninstallExtension(extension.pkgName) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UpdateExtension.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UpdateExtension.kt index 655b1a2e..8e4a9821 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UpdateExtension.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/interactor/UpdateExtension.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.extension.interactor import ca.gosyer.jui.domain.extension.model.Extension -import ca.gosyer.jui.domain.extension.service.ExtensionRepositoryOld +import ca.gosyer.jui.domain.extension.service.ExtensionRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class UpdateExtension @Inject constructor( - private val extensionRepositoryOld: ExtensionRepositoryOld, + private val extensionRepository: ExtensionRepository, ) { suspend fun await( extension: Extension, @@ -28,7 +28,7 @@ class UpdateExtension } .collect() - fun asFlow(extension: Extension) = extensionRepositoryOld.updateExtension(extension.pkgName) + fun asFlow(extension: Extension) = extensionRepository.updateExtension(extension.pkgName) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/service/ExtensionRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/service/ExtensionRepository.kt new file mode 100644 index 00000000..8b1f98b6 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/extension/service/ExtensionRepository.kt @@ -0,0 +1,32 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.extension.service + +import ca.gosyer.jui.domain.extension.model.Extension +import kotlinx.coroutines.flow.Flow +import okio.Source + +interface ExtensionRepository { + + fun getExtensionList(): Flow> + + fun installExtension( + source: Source, + ): Flow + + fun installExtension( + pkgName: String, + ): Flow + + fun updateExtension( + pkgName: String, + ): Flow + + fun uninstallExtension( + pkgName: String, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt index 1d4bbc8b..1a43770a 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/GetGlobalMeta.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.global.interactor -import ca.gosyer.jui.domain.global.service.GlobalRepositoryOld +import ca.gosyer.jui.domain.global.service.GlobalRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class GetGlobalMeta @Inject constructor( - private val globalRepositoryOld: GlobalRepositoryOld, + private val globalRepository: GlobalRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class GetGlobalMeta } .singleOrNull() - fun asFlow() = globalRepositoryOld.getGlobalMeta() + fun asFlow() = globalRepository.getGlobalMeta() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt index 7ec996e9..7f67d1fa 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/interactor/UpdateGlobalMeta.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.global.interactor import ca.gosyer.jui.domain.global.model.GlobalMeta -import ca.gosyer.jui.domain.global.service.GlobalRepositoryOld +import ca.gosyer.jui.domain.global.service.GlobalRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow @@ -17,7 +17,7 @@ import org.lighthousegames.logging.logging class UpdateGlobalMeta @Inject constructor( - private val globalRepositoryOld: GlobalRepositoryOld, + private val globalRepository: GlobalRepository, ) { suspend fun await( globalMeta: GlobalMeta, @@ -35,7 +35,7 @@ class UpdateGlobalMeta example: Int = globalMeta.example, ) = flow { if (example != globalMeta.example) { - globalRepositoryOld.updateGlobalMeta( + globalRepository.updateGlobalMeta( "example", example.toString(), ).collect() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt new file mode 100644 index 00000000..5089787c --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/global/service/GlobalRepository.kt @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.global.service + +import ca.gosyer.jui.domain.global.model.GlobalMeta +import kotlinx.coroutines.flow.Flow + +interface GlobalRepository { + fun getGlobalMeta(): Flow + + fun updateGlobalMeta( + key: String, + value: String, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/AddMangaToLibrary.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/AddMangaToLibrary.kt index ca25bc06..dab8756f 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/AddMangaToLibrary.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/AddMangaToLibrary.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.library.interactor import ca.gosyer.jui.domain.ServerListeners -import ca.gosyer.jui.domain.library.service.LibraryRepositoryOld +import ca.gosyer.jui.domain.library.service.LibraryRepository import ca.gosyer.jui.domain.manga.model.Manga import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging class AddMangaToLibrary @Inject constructor( - private val libraryRepositoryOld: LibraryRepositoryOld, + private val libraryRepository: LibraryRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -42,11 +42,11 @@ class AddMangaToLibrary .singleOrNull() fun asFlow(mangaId: Long) = - libraryRepositoryOld.addMangaToLibrary(mangaId) + libraryRepository.addMangaToLibrary(mangaId) .onEach { serverListeners.updateManga(mangaId) } fun asFlow(manga: Manga) = - libraryRepositoryOld.addMangaToLibrary(manga.id) + libraryRepository.addMangaToLibrary(manga.id) .onEach { serverListeners.updateManga(manga.id) } companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/RemoveMangaFromLibrary.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/RemoveMangaFromLibrary.kt index 0597f028..7f68749d 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/RemoveMangaFromLibrary.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/interactor/RemoveMangaFromLibrary.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.library.interactor import ca.gosyer.jui.domain.ServerListeners -import ca.gosyer.jui.domain.library.service.LibraryRepositoryOld +import ca.gosyer.jui.domain.library.service.LibraryRepository import ca.gosyer.jui.domain.manga.model.Manga import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging class RemoveMangaFromLibrary @Inject constructor( - private val libraryRepositoryOld: LibraryRepositoryOld, + private val libraryRepository: LibraryRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -42,11 +42,11 @@ class RemoveMangaFromLibrary .singleOrNull() fun asFlow(mangaId: Long) = - libraryRepositoryOld.removeMangaFromLibrary(mangaId) + libraryRepository.removeMangaFromLibrary(mangaId) .onEach { serverListeners.updateManga(mangaId) } fun asFlow(manga: Manga) = - libraryRepositoryOld.removeMangaFromLibrary(manga.id) + libraryRepository.removeMangaFromLibrary(manga.id) .onEach { serverListeners.updateManga(manga.id) } companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/service/LibraryRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/service/LibraryRepository.kt new file mode 100644 index 00000000..8778e3e6 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/library/service/LibraryRepository.kt @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.library.service + +import kotlinx.coroutines.flow.Flow + +interface LibraryRepository { + fun addMangaToLibrary( + mangaId: Long, + ): Flow + + fun removeMangaFromLibrary( + mangaId: Long, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetManga.kt index cfca7d4d..c3a93b38 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetManga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetManga.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.manga.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.manga.model.Manga -import ca.gosyer.jui.domain.manga.service.MangaRepositoryOld +import ca.gosyer.jui.domain.manga.service.MangaRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.take @@ -18,7 +18,7 @@ import org.lighthousegames.logging.logging class GetManga @Inject constructor( - private val mangaRepositoryOld: MangaRepositoryOld, + private val mangaRepository: MangaRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -45,12 +45,12 @@ class GetManga fun asFlow(mangaId: Long) = serverListeners.combineMangaUpdates( - mangaRepositoryOld.getManga(mangaId), + mangaRepository.getManga(mangaId), ) { mangaId in it } fun asFlow(manga: Manga) = serverListeners.combineMangaUpdates( - mangaRepositoryOld.getManga(manga.id), + mangaRepository.getManga(manga.id), ) { manga.id in it } companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt deleted file mode 100644 index f3c170e5..00000000 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/GetMangaFull.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package ca.gosyer.jui.domain.manga.interactor - -import ca.gosyer.jui.domain.ServerListeners -import ca.gosyer.jui.domain.manga.model.Manga -import ca.gosyer.jui.domain.manga.service.MangaRepositoryOld -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.singleOrNull -import kotlinx.coroutines.flow.take -import me.tatarka.inject.annotations.Inject -import org.lighthousegames.logging.logging - -class GetMangaFull - @Inject - constructor( - private val mangaRepositoryOld: MangaRepositoryOld, - private val serverListeners: ServerListeners, - ) { - suspend fun await( - mangaId: Long, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(mangaId) - .take(1) - .catch { - onError(it) - log.warn(it) { "Failed to get full manga $mangaId" } - } - .singleOrNull() - - suspend fun await( - manga: Manga, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(manga) - .take(1) - .catch { - onError(it) - log.warn(it) { "Failed to get full manga ${manga.title}(${manga.id})" } - } - .singleOrNull() - - fun asFlow(mangaId: Long) = - serverListeners.combineMangaUpdates( - mangaRepositoryOld.getMangaFull(mangaId), - ) { mangaId in it } - - fun asFlow(manga: Manga) = - serverListeners.combineMangaUpdates( - mangaRepositoryOld.getMangaFull(manga.id), - ) { manga.id in it } - - companion object { - private val log = logging() - } - } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshManga.kt index e9e04c8e..26911fb1 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshManga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshManga.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.manga.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.manga.model.Manga -import ca.gosyer.jui.domain.manga.service.MangaRepositoryOld +import ca.gosyer.jui.domain.manga.service.MangaRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.singleOrNull @@ -19,7 +19,7 @@ import org.lighthousegames.logging.logging class RefreshManga @Inject constructor( - private val mangaRepositoryOld: MangaRepositoryOld, + private val mangaRepository: MangaRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -44,9 +44,9 @@ class RefreshManga } .singleOrNull() - fun asFlow(mangaId: Long) = mangaRepositoryOld.getManga(mangaId, true).onEach { serverListeners.updateManga(mangaId) } + fun asFlow(mangaId: Long) = mangaRepository.refreshManga(mangaId).onEach { serverListeners.updateManga(mangaId) } - fun asFlow(manga: Manga) = mangaRepositoryOld.getManga(manga.id, true).onEach { serverListeners.updateManga(manga.id) } + fun asFlow(manga: Manga) = mangaRepository.refreshManga(manga.id).onEach { serverListeners.updateManga(manga.id) } companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt deleted file mode 100644 index 4f9201c1..00000000 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/RefreshMangaFull.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package ca.gosyer.jui.domain.manga.interactor - -import ca.gosyer.jui.domain.ServerListeners -import ca.gosyer.jui.domain.manga.model.Manga -import ca.gosyer.jui.domain.manga.service.MangaRepositoryOld -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.singleOrNull -import kotlinx.coroutines.flow.take -import me.tatarka.inject.annotations.Inject -import org.lighthousegames.logging.logging - -class RefreshMangaFull - @Inject - constructor( - private val mangaRepositoryOld: MangaRepositoryOld, - private val serverListeners: ServerListeners, - ) { - suspend fun await( - mangaId: Long, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(mangaId) - .take(1) - .catch { - onError(it) - log.warn(it) { "Failed to refresh full manga $mangaId" } - } - .singleOrNull() - - suspend fun await( - manga: Manga, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(manga) - .take(1) - .catch { - onError(it) - log.warn(it) { "Failed to refresh full manga ${manga.title}(${manga.id})" } - } - .singleOrNull() - - fun asFlow(mangaId: Long) = mangaRepositoryOld.getMangaFull(mangaId, true).onEach { serverListeners.updateManga(mangaId) } - - fun asFlow(manga: Manga) = mangaRepositoryOld.getMangaFull(manga.id, true).onEach { serverListeners.updateManga(manga.id) } - - companion object { - private val log = logging() - } - } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/UpdateMangaMeta.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/UpdateMangaMeta.kt index 286f72df..5f6af7f7 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/UpdateMangaMeta.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/interactor/UpdateMangaMeta.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.domain.manga.interactor import ca.gosyer.jui.domain.ServerListeners import ca.gosyer.jui.domain.manga.model.Manga -import ca.gosyer.jui.domain.manga.service.MangaRepositoryOld +import ca.gosyer.jui.domain.manga.service.MangaRepository import io.ktor.http.decodeURLQueryComponent import io.ktor.http.encodeURLQueryComponent import kotlinx.coroutines.flow.catch @@ -20,7 +20,7 @@ import org.lighthousegames.logging.logging class UpdateMangaMeta @Inject constructor( - private val mangaRepositoryOld: MangaRepositoryOld, + private val mangaRepository: MangaRepository, private val serverListeners: ServerListeners, ) { suspend fun await( @@ -39,7 +39,7 @@ class UpdateMangaMeta readerMode: String = manga.meta.juiReaderMode.decodeURLQueryComponent(), ) = flow { if (readerMode.encodeURLQueryComponent() != manga.meta.juiReaderMode) { - mangaRepositoryOld.updateMangaMeta( + mangaRepository.updateMangaMeta( manga.id, "juiReaderMode", readerMode, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt index 29a60857..024ae85c 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/model/Manga.kt @@ -8,7 +8,6 @@ package ca.gosyer.jui.domain.manga.model import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import ca.gosyer.jui.domain.chapter.model.Chapter import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.i18n.MR import dev.icerock.moko.resources.StringResource @@ -42,7 +41,7 @@ data class Manga( val unreadCount: Int?, val downloadCount: Int?, val chapterCount: Int?, - val lastChapterRead: Chapter? = null, + val lastChapterReadTime: Long? = null, val age: Long?, val chaptersAge: Long?, ) diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt new file mode 100644 index 00000000..b6f47530 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/manga/service/MangaRepository.kt @@ -0,0 +1,38 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.manga.service + +import ca.gosyer.jui.domain.manga.model.Manga +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.flow.Flow + +interface MangaRepository { + + fun getManga( + mangaId: Long, + ): Flow + + fun refreshManga( + mangaId: Long, + ): Flow + + fun getMangaLibrary( + mangaId: Long, + ): Flow + + fun getMangaThumbnail( + mangaId: Long, + block: HttpRequestBuilder.() -> Unit, + ): Flow + + fun updateMangaMeta( + mangaId: Long, + key: String, + value: String, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/AuthMode.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/AuthMode.kt new file mode 100644 index 00000000..2e0f0fa7 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/AuthMode.kt @@ -0,0 +1,9 @@ +package ca.gosyer.jui.domain.settings.model + +enum class AuthMode { + NONE, + BASIC_AUTH, + SIMPLE_LOGIN, + UI_LOGIN, + UNKNOWN__, +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DatabaseType.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DatabaseType.kt new file mode 100644 index 00000000..3adf3a3d --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DatabaseType.kt @@ -0,0 +1,8 @@ +package ca.gosyer.jui.domain.settings.model + +enum class DatabaseType { + H2, + POSTGRESQL, + UNKNOWN__, + ; +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DownloadConversion.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DownloadConversion.kt new file mode 100644 index 00000000..3a2537cd --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/DownloadConversion.kt @@ -0,0 +1,7 @@ +package ca.gosyer.jui.domain.settings.model + +class DownloadConversion( + val compressionLevel: Double?, + val mimeType: String, + val target: String, +) diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncChecksumMethod.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncChecksumMethod.kt new file mode 100644 index 00000000..5a522bad --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncChecksumMethod.kt @@ -0,0 +1,7 @@ +package ca.gosyer.jui.domain.settings.model + +enum class KoreaderSyncChecksumMethod { + BINARY, + FILENAME, + UNKNOWN__, +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncConflictStrategy.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncConflictStrategy.kt new file mode 100644 index 00000000..9f020318 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/KoreaderSyncConflictStrategy.kt @@ -0,0 +1,9 @@ +package ca.gosyer.jui.domain.settings.model + +enum class KoreaderSyncConflictStrategy { + PROMPT, + KEEP_LOCAL, + KEEP_REMOTE, + DISABLED, + UNKNOWN__, +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SetSettingsInput.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SetSettingsInput.kt index 2a82aa99..bef8cb5c 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SetSettingsInput.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SetSettingsInput.kt @@ -7,17 +7,23 @@ package ca.gosyer.jui.domain.settings.model class SetSettingsInput( + val authMode: AuthMode? = null, + val authPassword: String? = null, + val authUsername: String? = null, + val autoDownloadIgnoreReUploads: Boolean? = null, val autoDownloadNewChapters: Boolean? = null, val autoDownloadNewChaptersLimit: Int? = null, val backupInterval: Int? = null, val backupPath: String? = null, val backupTTL: Int? = null, val backupTime: String? = null, - val basicAuthEnabled: Boolean? = null, - val basicAuthPassword: String? = null, - val basicAuthUsername: String? = null, + val databasePassword: String? = null, + val databaseType: DatabaseType? = null, + val databaseUrl: String? = null, + val databaseUsername: String? = null, val debugLogsEnabled: Boolean? = null, val downloadAsCbz: Boolean? = null, + val downloadConversions: List? = null, val downloadsPath: String? = null, val electronPath: String? = null, val excludeCompleted: Boolean? = null, @@ -25,17 +31,38 @@ class SetSettingsInput( val excludeNotStarted: Boolean? = null, val excludeUnreadChapters: Boolean? = null, val extensionRepos: List? = null, + val flareSolverrAsResponseFallback: Boolean? = null, val flareSolverrEnabled: Boolean? = null, val flareSolverrSessionName: String? = null, val flareSolverrSessionTtl: Int? = null, val flareSolverrTimeout: Int? = null, val flareSolverrUrl: String? = null, val globalUpdateInterval: Double? = null, - val gqlDebugLogsEnabled: Boolean? = null, val initialOpenInBrowserEnabled: Boolean? = null, val ip: String? = null, + val jwtAudience: String? = null, + val jwtRefreshExpiry: Any? = null, + val jwtTokenExpiry: Any? = null, + val koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod? = null, + val koreaderSyncDeviceId: String? = null, + val koreaderSyncPercentageTolerance: Double? = null, + val koreaderSyncServerUrl: String? = null, + val koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy? = null, + val koreaderSyncStrategyForward: KoreaderSyncConflictStrategy? = null, + val koreaderSyncUserkey: String? = null, + val koreaderSyncUsername: String? = null, val localSourcePath: String? = null, + val maxLogFileSize: String? = null, + val maxLogFiles: Int? = null, + val maxLogFolderSize: String? = null, val maxSourcesInParallel: Int? = null, + val opdsChapterSortOrder: SortOrder? = null, + val opdsEnablePageReadProgress: Boolean? = null, + val opdsItemsPerPage: Int? = null, + val opdsMarkAsReadOnDownload: Boolean? = null, + val opdsShowOnlyDownloadedChapters: Boolean? = null, + val opdsShowOnlyUnreadChapters: Boolean? = null, + val opdsUseBinaryFileSizes: Boolean? = null, val port: Int? = null, val socksProxyEnabled: Boolean? = null, val socksProxyHost: String? = null, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/Settings.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/Settings.kt index c862fab9..0d7dcde3 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/Settings.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/Settings.kt @@ -10,17 +10,23 @@ import androidx.compose.runtime.Stable @Stable class Settings( + val authMode: AuthMode, + val authPassword: String, + val authUsername: String, + val autoDownloadIgnoreReUploads: Boolean, val autoDownloadNewChapters: Boolean, val autoDownloadNewChaptersLimit: Int, val backupInterval: Int, val backupPath: String, val backupTTL: Int, val backupTime: String, - val basicAuthEnabled: Boolean, - val basicAuthPassword: String, - val basicAuthUsername: String, + val databasePassword: String, + val databaseType: DatabaseType, + val databaseUrl: String, + val databaseUsername: String, val debugLogsEnabled: Boolean, val downloadAsCbz: Boolean, + val downloadConversions: List, val downloadsPath: String, val electronPath: String, val excludeCompleted: Boolean, @@ -28,17 +34,38 @@ class Settings( val excludeNotStarted: Boolean, val excludeUnreadChapters: Boolean, val extensionRepos: List, + val flareSolverrAsResponseFallback: Boolean, val flareSolverrEnabled: Boolean, val flareSolverrSessionName: String, val flareSolverrSessionTtl: Int, val flareSolverrTimeout: Int, val flareSolverrUrl: String, val globalUpdateInterval: Double, - val gqlDebugLogsEnabled: Boolean, val initialOpenInBrowserEnabled: Boolean, val ip: String, + val jwtAudience: String, + val jwtRefreshExpiry: Any, + val jwtTokenExpiry: Any, + val koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod, + val koreaderSyncDeviceId: String, + val koreaderSyncPercentageTolerance: Double, + val koreaderSyncServerUrl: String, + val koreaderSyncStrategyBackward: KoreaderSyncConflictStrategy, + val koreaderSyncStrategyForward: KoreaderSyncConflictStrategy, + val koreaderSyncUserkey: String, + val koreaderSyncUsername: String, val localSourcePath: String, + val maxLogFileSize: String, + val maxLogFiles: Int, + val maxLogFolderSize: String, val maxSourcesInParallel: Int, + val opdsChapterSortOrder: SortOrder, + val opdsEnablePageReadProgress: Boolean, + val opdsItemsPerPage: Int, + val opdsMarkAsReadOnDownload: Boolean, + val opdsShowOnlyDownloadedChapters: Boolean, + val opdsShowOnlyUnreadChapters: Boolean, + val opdsUseBinaryFileSizes: Boolean, val port: Int, val socksProxyEnabled: Boolean, val socksProxyHost: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SortOrder.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SortOrder.kt new file mode 100644 index 00000000..6369b6e7 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/settings/model/SortOrder.kt @@ -0,0 +1,11 @@ +package ca.gosyer.jui.domain.settings.model + +enum class SortOrder { + ASC, + DESC, + ASC_NULLS_FIRST, + DESC_NULLS_FIRST, + ASC_NULLS_LAST, + DESC_NULLS_LAST, + UNKNOWN__, +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetFilterList.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetFilterList.kt index 9d83a7f2..4d2a9fbf 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetFilterList.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetFilterList.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.source.interactor import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -16,39 +16,35 @@ import org.lighthousegames.logging.logging class GetFilterList @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await( source: Source, - reset: Boolean, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(source.id, reset) + ) = asFlow(source.id) .catch { onError(it) - log.warn(it) { "Failed to get filter list for ${source.displayName} with reset = $reset" } + log.warn(it) { "Failed to get filter list for ${source.displayName}" } } .singleOrNull() suspend fun await( sourceId: Long, - reset: Boolean, onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(sourceId, reset) + ) = asFlow(sourceId) .catch { onError(it) - log.warn(it) { "Failed to get filter list for $sourceId with reset = $reset" } + log.warn(it) { "Failed to get filter list for $sourceId" } } .singleOrNull() fun asFlow( source: Source, - reset: Boolean, - ) = sourceRepositoryOld.getFilterList(source.id, reset) + ) = sourceRepository.getFilterList(source.id) fun asFlow( sourceId: Long, - reset: Boolean, - ) = sourceRepositoryOld.getFilterList(sourceId, reset) + ) = sourceRepository.getFilterList(sourceId) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetLatestManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetLatestManga.kt index 304d6028..a5cabf65 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetLatestManga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetLatestManga.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.source.interactor import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class GetLatestManga @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await( source: Source, @@ -43,12 +43,12 @@ class GetLatestManga fun asFlow( source: Source, page: Int, - ) = sourceRepositoryOld.getLatestManga(source.id, page) + ) = sourceRepository.getLatestManga(source.id, page) fun asFlow( sourceId: Long, page: Int, - ) = sourceRepositoryOld.getLatestManga(sourceId, page) + ) = sourceRepository.getLatestManga(sourceId, page) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetPopularManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetPopularManga.kt index ce73fce1..2fd6b75f 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetPopularManga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetPopularManga.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.source.interactor import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class GetPopularManga @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await( source: Source, @@ -43,12 +43,12 @@ class GetPopularManga fun asFlow( source: Source, page: Int, - ) = sourceRepositoryOld.getPopularManga(source.id, page) + ) = sourceRepository.getPopularManga(source.id, page) fun asFlow( sourceId: Long, page: Int, - ) = sourceRepositoryOld.getPopularManga(sourceId, page) + ) = sourceRepository.getPopularManga(sourceId, page) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt deleted file mode 100644 index e213cc09..00000000 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetQuickSearchManga.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package ca.gosyer.jui.domain.source.interactor - -import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange -import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterData -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.singleOrNull -import me.tatarka.inject.annotations.Inject -import org.lighthousegames.logging.logging - -class GetQuickSearchManga - @Inject - constructor( - private val sourceRepositoryOld: SourceRepositoryOld, - ) { - suspend fun await( - source: Source, - searchTerm: String?, - page: Int, - filters: List?, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(source.id, searchTerm, page, filters) - .catch { - onError(it) - log.warn(it) { "Failed to get quick search results from ${source.displayName} on page $page with query '$searchTerm'" } - } - .singleOrNull() - - suspend fun await( - sourceId: Long, - searchTerm: String?, - page: Int, - filters: List?, - onError: suspend (Throwable) -> Unit = {}, - ) = asFlow(sourceId, searchTerm, page, filters) - .catch { - onError(it) - log.warn(it) { "Failed to get quick search results from $sourceId on page $page with query '$searchTerm'" } - } - .singleOrNull() - - fun asFlow( - source: Source, - searchTerm: String?, - page: Int, - filters: List?, - ) = sourceRepositoryOld.getQuickSearchResults( - source.id, - page, - SourceFilterData( - searchTerm?.ifBlank { null }, - filters?.ifEmpty { null }, - ), - ) - - fun asFlow( - sourceId: Long, - searchTerm: String?, - page: Int, - filters: List?, - ) = sourceRepositoryOld.getQuickSearchResults( - sourceId, - page, - SourceFilterData( - searchTerm?.ifBlank { null }, - filters?.ifEmpty { null }, - ), - ) - - companion object { - private val log = logging() - } - } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSearchManga.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSearchManga.kt index dde0077e..9579957a 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSearchManga.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSearchManga.kt @@ -7,7 +7,8 @@ package ca.gosyer.jui.domain.source.interactor import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -16,15 +17,16 @@ import org.lighthousegames.logging.logging class GetSearchManga @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await( source: Source, - searchTerm: String?, page: Int, + searchTerm: String?, + filters: List?, onError: suspend (Throwable) -> Unit = { }, - ) = asFlow(source.id, searchTerm, page) + ) = asFlow(source.id, page, searchTerm, filters) .catch { onError(it) log.warn(it) { "Failed to get search results from ${source.displayName} on page $page with query '$searchTerm'" } @@ -35,9 +37,10 @@ class GetSearchManga sourceId: Long, searchTerm: String?, page: Int, + filters: List?, onError: suspend (Throwable) -> Unit = { }, - ) = asFlow(sourceId, searchTerm, page) + ) = asFlow(sourceId, page, searchTerm, filters) .catch { onError(it) log.warn(it) { "Failed to get search results from $sourceId on page $page with query '$searchTerm'" } @@ -46,22 +49,26 @@ class GetSearchManga fun asFlow( source: Source, - searchTerm: String?, page: Int, - ) = sourceRepositoryOld.getSearchResults( + searchTerm: String?, + filters: List?, + ) = sourceRepository.getSearchResults( source.id, - searchTerm?.ifBlank { null }, page, + searchTerm?.ifBlank { null }, + filters, ) fun asFlow( sourceId: Long, - searchTerm: String?, page: Int, - ) = sourceRepositoryOld.getSearchResults( + searchTerm: String?, + filters: List?, + ) = sourceRepository.getSearchResults( sourceId, - searchTerm?.ifBlank { null }, page, + searchTerm?.ifBlank { null }, + filters, ) companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceList.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceList.kt index 1214be64..4954093c 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceList.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceList.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.source.interactor -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class GetSourceList @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class GetSourceList } .singleOrNull() - fun asFlow() = sourceRepositoryOld.getSourceList() + fun asFlow() = sourceRepository.getSourceList() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceSettings.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceSettings.kt index 366586bc..c77c2d81 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceSettings.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/GetSourceSettings.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.source.interactor import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class GetSourceSettings @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { suspend fun await( source: Source, @@ -38,9 +38,9 @@ class GetSourceSettings } .singleOrNull() - fun asFlow(source: Source) = sourceRepositoryOld.getSourceSettings(source.id) + fun asFlow(source: Source) = sourceRepository.getSourceSettings(source.id) - fun asFlow(sourceId: Long) = sourceRepositoryOld.getSourceSettings(sourceId) + fun asFlow(sourceId: Long) = sourceRepository.getSourceSettings(sourceId) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceFilter.kt deleted file mode 100644 index a4396ec6..00000000 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceFilter.kt +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package ca.gosyer.jui.domain.source.interactor - -import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import me.tatarka.inject.annotations.Inject -import org.lighthousegames.logging.logging - -class SetSourceFilter - @Inject - constructor( - private val sourceRepositoryOld: SourceRepositoryOld, - ) { - suspend fun await( - source: Source, - filterIndex: Int, - filter: Any, - onError: suspend (Throwable) -> Unit = { - }, - ) = asFlow(source, filterIndex, filter) - .catch { - onError(it) - log.warn(it) { "Failed to set filter for ${source.displayName} with index = $filterIndex and value = $filter" } - } - .collect() - - suspend fun await( - sourceId: Long, - filterIndex: Int, - filter: Any, - onError: suspend (Throwable) -> Unit = { - }, - ) = asFlow(sourceId, filterIndex, filter) - .catch { - onError(it) - log.warn(it) { "Failed to set filter for $sourceId with index = $filterIndex and value = $filter" } - } - .collect() - - suspend fun await( - source: Source, - filterIndex: Int, - childFilterIndex: Int, - filter: Any, - onError: suspend (Throwable) -> Unit = { - }, - ) = asFlow(source, filterIndex, childFilterIndex, filter) - .catch { - onError(it) - log.warn(it) { - "Failed to set filter for ${source.displayName} with index = $filterIndex " + - "and childIndex = $childFilterIndex and value = $filter" - } - } - .collect() - - suspend fun await( - sourceId: Long, - filterIndex: Int, - childFilterIndex: Int, - filter: Any, - onError: suspend (Throwable) -> Unit = { - }, - ) = asFlow(sourceId, filterIndex, childFilterIndex, filter) - .catch { - onError(it) - log.warn(it) { - "Failed to set filter for $sourceId with index = $filterIndex " + - "and childIndex = $childFilterIndex and value = $filter" - } - } - .collect() - - fun asFlow( - source: Source, - filterIndex: Int, - filter: Any, - ) = sourceRepositoryOld.setFilter( - source.id, - SourceFilterChange(filterIndex, filter), - ) - - fun asFlow( - sourceId: Long, - filterIndex: Int, - filter: Any, - ) = sourceRepositoryOld.setFilter( - sourceId, - SourceFilterChange(filterIndex, filter), - ) - - fun asFlow( - source: Source, - filterIndex: Int, - childFilterIndex: Int, - filter: Any, - ) = sourceRepositoryOld.setFilter( - source.id, - SourceFilterChange(filterIndex, Json.encodeToString(SourceFilterChange(childFilterIndex, filter))), - ) - - fun asFlow( - sourceId: Long, - filterIndex: Int, - childFilterIndex: Int, - filter: Any, - ) = sourceRepositoryOld.setFilter( - sourceId, - SourceFilterChange(filterIndex, Json.encodeToString(SourceFilterChange(childFilterIndex, filter))), - ) - - companion object { - private val log = logging() - } - } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceSetting.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceSetting.kt index ebfded0f..74fe3761 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceSetting.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/interactor/SetSourceSetting.kt @@ -6,9 +6,8 @@ package ca.gosyer.jui.domain.source.interactor -import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange -import ca.gosyer.jui.domain.source.service.SourceRepositoryOld +import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference +import ca.gosyer.jui.domain.source.service.SourceRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -17,50 +16,26 @@ import org.lighthousegames.logging.logging class SetSourceSetting @Inject constructor( - private val sourceRepositoryOld: SourceRepositoryOld, + private val sourceRepository: SourceRepository, ) { - suspend fun await( - source: Source, - settingIndex: Int, - setting: Any, - onError: suspend (Throwable) -> Unit = { - }, - ) = asFlow(source, settingIndex, setting) - .catch { - onError(it) - log.warn(it) { "Failed to set setting for ${source.displayName} with index = $settingIndex and value = $setting" } - } - .collect() - suspend fun await( sourceId: Long, - settingIndex: Int, - setting: Any, + sourcePreference: SourcePreference, onError: suspend (Throwable) -> Unit = { }, - ) = asFlow(sourceId, settingIndex, setting) + ) = asFlow(sourceId, sourcePreference) .catch { onError(it) - log.warn(it) { "Failed to set setting for $sourceId with index = $settingIndex and value = $setting" } + log.warn(it) { "Failed to set setting for $sourceId with index = ${sourcePreference.position}" } } .collect() - fun asFlow( - source: Source, - settingIndex: Int, - setting: Any, - ) = sourceRepositoryOld.setSourceSetting( - source.id, - SourcePreferenceChange(settingIndex, setting), - ) - fun asFlow( sourceId: Long, - settingIndex: Int, - setting: Any, - ) = sourceRepositoryOld.setSourceSetting( + sourcePreference: SourcePreference, + ) = sourceRepository.setSourceSetting( sourceId, - SourcePreferenceChange(settingIndex, setting), + sourcePreference, ) companion object { diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilterOld.kt similarity index 91% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilterOld.kt index 0c6b6666..f45531c8 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/CheckBoxFilterOld.kt @@ -11,9 +11,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("CheckBox") -data class CheckBoxFilter( +data class CheckBoxFilterOld( override val filter: CheckBoxProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class CheckBoxProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilterOld.kt similarity index 78% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilterOld.kt index 4eb54975..cc0cca96 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/GroupFilterOld.kt @@ -11,12 +11,12 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("Group") -data class GroupFilter( +data class GroupFilterOld( override val filter: GroupProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class GroupProps( override val name: String, - override val state: List, - ) : Props> + override val state: List, + ) : Props> } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilterOld.kt similarity index 92% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilterOld.kt index 74e40782..ae76b670 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/HeaderFilterOld.kt @@ -12,9 +12,9 @@ import kotlinx.serialization.Transient @Serializable @SerialName("Header") -data class HeaderFilter( +data class HeaderFilterOld( override val filter: HeaderProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class HeaderProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilterOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilterOld.kt index 02e4d1d7..83722589 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SelectFilterOld.kt @@ -12,9 +12,9 @@ import kotlinx.serialization.json.JsonElement @Serializable @SerialName("Select") -data class SelectFilter( +data class SelectFilterOld( override val filter: SelectProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class SelectProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilterOld.kt similarity index 91% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilterOld.kt index 654cf480..ad725638 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SeparatorFilterOld.kt @@ -12,9 +12,9 @@ import kotlinx.serialization.Transient @Serializable @SerialName("Separator") -data class SeparatorFilter( +data class SeparatorFilterOld( override val filter: SeparatorProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class SeparatorProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilterOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilterOld.kt index 35fdc818..e460d10b 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SortFilterOld.kt @@ -11,9 +11,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("Sort") -data class SortFilter( +data class SortFilterOld( override val filter: SortProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class SortProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterChange.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterChange.kt index 0ef1c5e2..4b69de3e 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterChange.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterChange.kt @@ -11,16 +11,84 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @Serializable -data class SourceFilterChange( +data class SourceFilterChangeOld( val position: Int, val state: String, ) { constructor(position: Int, state: Any) : this( position, - if (state is SortFilter.Selection) { + if (state is SortFilterOld.Selection) { Json.encodeToString(state) } else { state.toString() }, ) } + + +sealed interface SourceFilter { + val position: Int + data class Checkbox( + override val position: Int, + val name: String, + val default: Boolean, + val value: Boolean = default, + ): SourceFilter + + data class Header( + override val position: Int, + val name: String, + ): SourceFilter + + data class Separator( + override val position: Int, + val name: String, + ): SourceFilter + + data class Group( + override val position: Int, + val name: String, + val value: List, + ): SourceFilter + + data class Select( + override val position: Int, + val name: String, + val values: List, + val default: Int, + val value: Int = default, + ): SourceFilter + + data class Sort( + override val position: Int, + val name: String, + val values: List, + val default: SelectionChange?, + val value: SelectionChange? = default, + ): SourceFilter { + data class SelectionChange( + val ascending: Boolean, + val index: Int + ) + } + + data class Text( + override val position: Int, + val name: String, + val default: String, + val value: String = default, + ): SourceFilter + + data class TriState( + override val position: Int, + val name: String, + val default: TriStateValue, + val value: TriStateValue = default, + ): SourceFilter { + enum class TriStateValue { + IGNORE, + INCLUDE, + EXCLUDE + } + } +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt index d1d06ce1..24c54fc9 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterData.kt @@ -11,5 +11,5 @@ import kotlinx.serialization.Serializable @Serializable data class SourceFilterData( val searchTerm: String?, - val filter: List?, + val filter: List?, ) diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterOld.kt index a708c2bb..7b7ab9e7 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/SourceFilterOld.kt @@ -9,7 +9,7 @@ package ca.gosyer.jui.domain.source.model.sourcefilters import kotlinx.serialization.Serializable @Serializable -sealed class SourceFilter { +sealed class SourceFilterOld { abstract val filter: Props<*> } diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilterOld.kt similarity index 91% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilterOld.kt index 1e0b352b..c28aaec5 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TextFilterOld.kt @@ -11,9 +11,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("Text") -data class TextFilter( +data class TextFilterOld( override val filter: TextProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class TextProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilter.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilterOld.kt similarity index 91% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilter.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilterOld.kt index 9c9a15cf..0c6f0200 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilter.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcefilters/TriStateFilterOld.kt @@ -11,9 +11,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("TriState") -data class TriStateFilter( +data class TriStateFilterOld( override val filter: TriStateProps, -) : SourceFilter() { +) : SourceFilterOld() { @Serializable data class TriStateProps( override val name: String, diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreferenceOld.kt similarity index 89% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreference.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreferenceOld.kt index 04b58b1e..fbb229e8 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/CheckBoxPreferenceOld.kt @@ -13,6 +13,6 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("CheckBoxPreference") @Immutable -data class CheckBoxPreference( +data class CheckBoxPreferenceOld( override val props: TwoStateProps, -) : SourcePreference() +) : SourcePreferenceOld() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreferenceOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreference.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreferenceOld.kt index ccdd9a24..e88dd3e4 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/EditTextPreferenceOld.kt @@ -13,9 +13,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("EditTextPreference") @Immutable -data class EditTextPreference( +data class EditTextPreferenceOld( override val props: EditTextProps, -) : SourcePreference() { +) : SourcePreferenceOld() { @Serializable @Immutable data class EditTextProps( diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreferenceOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreference.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreferenceOld.kt index 3bca70f3..e55c2c09 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/ListPreferenceOld.kt @@ -13,9 +13,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("ListPreference") @Immutable -data class ListPreference( +data class ListPreferenceOld( override val props: ListProps, -) : SourcePreference() { +) : SourcePreferenceOld() { @Serializable @Immutable data class ListProps( diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreferenceOld.kt similarity index 93% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreference.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreferenceOld.kt index 292d51b4..db6fd087 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/MultiSelectListPreferenceOld.kt @@ -13,9 +13,9 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("MultiSelectListPreference") @Immutable -data class MultiSelectListPreference( +data class MultiSelectListPreferenceOld( override val props: MultiSelectListProps, -) : SourcePreference() { +) : SourcePreferenceOld() { @Serializable @Immutable data class MultiSelectListProps( diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SourcePreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SourcePreference.kt index 87b58456..f8a6bc97 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SourcePreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SourcePreference.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable @Serializable @Immutable -sealed class SourcePreference { +sealed class SourcePreferenceOld { abstract val props: Props<*> } @@ -24,3 +24,71 @@ interface Props { val defaultValue: T val defaultValueType: String } + +sealed interface SourcePreference { + val position: Int +} + +data class SwitchSourcePreference( + override val position: Int, + val key: String?, + val title: String?, + val summary: String?, + val visible: Boolean, + val enabled: Boolean, + val currentValue: Boolean?, + val default: Boolean, +) : SourcePreference + +data class CheckBoxSourcePreference( + override val position: Int, + val key: String?, + val title: String?, + val summary: String?, + val visible: Boolean, + val enabled: Boolean, + val currentValue: Boolean?, + val default: Boolean, +) : SourcePreference + +data class EditTextSourcePreference( + override val position: Int, + val key: String?, + val title: String?, + val summary: String?, + val visible: Boolean, + val enabled: Boolean, + val currentValue: String?, + val default: String?, + val dialogTitle: String?, + val dialogMessage: String?, + val text: String?, +) : SourcePreference + +data class ListSourcePreference( + override val position: Int, + val key: String?, + val title: String?, + val summary: String?, + val visible: Boolean, + val enabled: Boolean, + val currentValue: String?, + val default: String?, + val entries: List, + val entryValues: List, +) : SourcePreference + +data class MultiSelectListSourcePreference( + override val position: Int, + val key: String?, + val title: String?, + val summary: String?, + val visible: Boolean, + val enabled: Boolean, + val currentValue: List?, + val default: List?, + val dialogTitle: String?, + val dialogMessage: String?, + val entries: List, + val entryValues: List, +) : SourcePreference diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreference.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreferenceOld.kt similarity index 89% rename from domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreference.kt rename to domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreferenceOld.kt index 1398bb8c..d1576351 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreference.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/model/sourcepreference/SwitchPreferenceOld.kt @@ -13,6 +13,6 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("SwitchPreferenceCompat") @Immutable -data class SwitchPreference( +data class SwitchPreferenceOld( override val props: TwoStateProps, -) : SourcePreference() +) : SourcePreferenceOld() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt new file mode 100644 index 00000000..12dbedf6 --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepository.kt @@ -0,0 +1,52 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.source.service + +import ca.gosyer.jui.domain.source.model.MangaPage +import ca.gosyer.jui.domain.source.model.Source +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter +import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference +import kotlinx.coroutines.flow.Flow + +interface SourceRepository { + + fun getSourceList(): Flow> + + fun getSourceInfo( + sourceId: Long, + ): Flow + + fun getPopularManga( + sourceId: Long, + pageNum: Int, + ): Flow + + fun getLatestManga( + sourceId: Long, + pageNum: Int, + ): Flow + + fun getFilterList( + sourceId: Long, + ): Flow> + + fun getSearchResults( + sourceId: Long, + pageNum: Int, + searchTerm: String?, + filters: List?, + ): Flow + + fun getSourceSettings( + sourceId: Long, + ): Flow> + + fun setSourceSetting( + sourceId: Long, + sourcePreference: SourcePreference, + ): Flow +} diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepositoryOld.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepositoryOld.kt index adf5bab4..df10904d 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepositoryOld.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/source/service/SourceRepositoryOld.kt @@ -8,11 +8,11 @@ package ca.gosyer.jui.domain.source.service import ca.gosyer.jui.domain.source.model.MangaPage import ca.gosyer.jui.domain.source.model.Source -import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChangeOld import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterData -import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterOld import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange +import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceOld import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.Headers @@ -54,20 +54,20 @@ interface SourceRepositoryOld { fun getFilterList( @Path("sourceId") sourceId: Long, @Query("reset") reset: Boolean = false, - ): Flow> + ): Flow> @POST("api/v1/source/{sourceId}/filters") @Headers("Content-Type: application/json") fun setFilter( @Path("sourceId") sourceId: Long, - @Body sourceFilter: SourceFilterChange, + @Body sourceFilter: SourceFilterChangeOld, ): Flow @POST("api/v1/source/{sourceId}/filters") @Headers("Content-Type: application/json") fun setFilters( @Path("sourceId") sourceId: Long, - @Body sourceFilters: List, + @Body sourceFilters: List, ): Flow @POST("api/v1/source/{sourceId}/quick-search") @@ -81,7 +81,7 @@ interface SourceRepositoryOld { @GET("api/v1/source/{sourceId}/preferences") fun getSourceSettings( @Path("sourceId") sourceId: Long, - ): Flow> + ): Flow> @POST("api/v1/source/{sourceId}/preferences") @Headers("Content-Type: application/json") diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/GetRecentUpdates.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/GetRecentUpdates.kt index d3f4a151..01e65112 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/GetRecentUpdates.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/GetRecentUpdates.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.updates.interactor -import ca.gosyer.jui.domain.updates.service.UpdatesRepositoryOld +import ca.gosyer.jui.domain.updates.service.UpdatesRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.singleOrNull import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class GetRecentUpdates @Inject constructor( - private val updatesRepositoryOld: UpdatesRepositoryOld, + private val updatesRepository: UpdatesRepository, ) { suspend fun await( pageNum: Int, @@ -27,7 +27,7 @@ class GetRecentUpdates } .singleOrNull() - fun asFlow(pageNum: Int) = updatesRepositoryOld.getRecentUpdates(pageNum) + fun asFlow(pageNum: Int) = updatesRepository.getRecentUpdates(pageNum) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateCategory.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateCategory.kt index 5ef9c6d0..f4d26d60 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateCategory.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateCategory.kt @@ -7,7 +7,7 @@ package ca.gosyer.jui.domain.updates.interactor import ca.gosyer.jui.domain.category.model.Category -import ca.gosyer.jui.domain.updates.service.UpdatesRepositoryOld +import ca.gosyer.jui.domain.updates.service.UpdatesRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -16,7 +16,7 @@ import org.lighthousegames.logging.logging class UpdateCategory @Inject constructor( - private val updatesRepositoryOld: UpdatesRepositoryOld, + private val updatesRepository: UpdatesRepository, ) { suspend fun await( categoryId: Long, @@ -38,9 +38,9 @@ class UpdateCategory } .collect() - fun asFlow(categoryId: Long) = updatesRepositoryOld.updateCategory(categoryId) + fun asFlow(categoryId: Long) = updatesRepository.updateCategory(categoryId) - fun asFlow(category: Category) = updatesRepositoryOld.updateCategory(category.id) + fun asFlow(category: Category) = updatesRepository.updateCategory(category.id) companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateLibrary.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateLibrary.kt index a3b1dcb5..4e1e0d33 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateLibrary.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/interactor/UpdateLibrary.kt @@ -6,7 +6,7 @@ package ca.gosyer.jui.domain.updates.interactor -import ca.gosyer.jui.domain.updates.service.UpdatesRepositoryOld +import ca.gosyer.jui.domain.updates.service.UpdatesRepository import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import me.tatarka.inject.annotations.Inject @@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging class UpdateLibrary @Inject constructor( - private val updatesRepositoryOld: UpdatesRepositoryOld, + private val updatesRepository: UpdatesRepository, ) { suspend fun await(onError: suspend (Throwable) -> Unit = {}) = asFlow() @@ -25,7 +25,7 @@ class UpdateLibrary } .collect() - fun asFlow() = updatesRepositoryOld.updateLibrary() + fun asFlow() = updatesRepository.updateLibrary() companion object { private val log = logging() diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/service/UpdatesRepository.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/service/UpdatesRepository.kt new file mode 100644 index 00000000..fd8b5abe --- /dev/null +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/updates/service/UpdatesRepository.kt @@ -0,0 +1,22 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.domain.updates.service + +import ca.gosyer.jui.domain.updates.model.Updates +import kotlinx.coroutines.flow.Flow + +interface UpdatesRepository { + fun getRecentUpdates( + pageNum: Int, + ): Flow + + fun updateLibrary(): Flow + + fun updateCategory( + categoryId: Long, + ): Flow +} diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt index be5af43d..4b1661f9 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/chapter/ChapterDownloadButtons.kt @@ -83,7 +83,7 @@ data class ChapterDownloadItem( } suspend fun stopDownloading(stopChapterDownload: StopChapterDownload) { - stopChapterDownload.await(chapter) + stopChapterDownload.await(chapter.id) _downloadState.value = ChapterDownloadState.NotDownloaded } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/categories/CategoriesScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/categories/CategoriesScreenViewModel.kt index ff95e032..4147c275 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/categories/CategoriesScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/categories/CategoriesScreenViewModel.kt @@ -74,11 +74,11 @@ class CategoriesScreenViewModel } } var updatedCategories = getCategories.await(true, onError = { toast(it.message.orEmpty()) }) - categories.forEach { category -> + categories.sortedBy { it.order }.forEach { category -> val updatedCategory = updatedCategories?.find { it.id == category.id || it.name == category.name } ?: return@forEach if (category.order != updatedCategory.order) { log.debug { "${category.name}: ${updatedCategory.order} to ${category.order}" } - reorderCategory.await(category.order, updatedCategory.order, onError = { toast(it.message.orEmpty()) }) + reorderCategory.await(category.id!!, category.order, onError = { toast(it.message.orEmpty()) }) } updatedCategories = getCategories.await(true, onError = { toast(it.message.orEmpty()) }) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/DownloadsScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/DownloadsScreenViewModel.kt index 10fcd174..ac5541ff 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/DownloadsScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/DownloadsScreenViewModel.kt @@ -71,14 +71,14 @@ class DownloadsScreenViewModel } fun stopDownload(chapter: Chapter) { - scope.launch { stopChapterDownload.await(chapter, onError = { toast(it.message.orEmpty()) }) } + scope.launch { stopChapterDownload.await(chapter.id, onError = { toast(it.message.orEmpty()) }) } } fun moveUp(chapter: Chapter) { scope.launch { val index = downloadQueue.value.indexOfFirst { it.mangaId == chapter.mangaId && it.chapterIndex == chapter.index } if (index == -1 || index <= 0) return@launch - reorderChapterDownload.await(chapter, index - 1, onError = { toast(it.message.orEmpty()) }) + reorderChapterDownload.await(chapter.id, index - 1, onError = { toast(it.message.orEmpty()) }) } } @@ -86,19 +86,19 @@ class DownloadsScreenViewModel scope.launch { val index = downloadQueue.value.indexOfFirst { it.mangaId == chapter.mangaId && it.chapterIndex == chapter.index } if (index == -1 || index >= downloadQueue.value.lastIndex) return@launch - reorderChapterDownload.await(chapter, index + 1, onError = { toast(it.message.orEmpty()) }) + reorderChapterDownload.await(chapter.id, index + 1, onError = { toast(it.message.orEmpty()) }) } } fun moveToTop(chapter: Chapter) { scope.launch { - reorderChapterDownload.await(chapter, 0, onError = { toast(it.message.orEmpty()) }) + reorderChapterDownload.await(chapter.id, 0, onError = { toast(it.message.orEmpty()) }) } } fun moveToBottom(chapter: Chapter) { scope.launch { - reorderChapterDownload.await(chapter, downloadQueue.value.lastIndex, onError = { toast(it.message.orEmpty()) }) + reorderChapterDownload.await(chapter.id, downloadQueue.value.lastIndex, onError = { toast(it.message.orEmpty()) }) } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt index 64556d09..9dd7858d 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/MangaScreenViewModel.kt @@ -137,6 +137,15 @@ class MangaScreenViewModel } .onEach { _manga.value = it + if (_manga.value?.initialized == false) { + refreshManga.await( + params.mangaId, + onError = { + log.warn(it) { "Error when fetching manga" } + toast(it.message.orEmpty()) + } + ) + } loadingManga.value = false } .catch { @@ -151,10 +160,20 @@ class MangaScreenViewModel } .onEach { _chapters.value = it.toDownloadChapters() + if (_chapters.value.isEmpty()) { + refreshChapters.await( + params.mangaId, + onError = { + log.warn(it) { "Error when fetching chapters" } + toast(it.message.orEmpty()) + } + ) + } loadingChapters.value = false } .catch { toast(it.message.orEmpty()) + log.warn(it) { "Error when getting chapters" } loadingChapters.value = false } .launchIn(scope) @@ -182,11 +201,25 @@ class MangaScreenViewModel fun refreshManga() { scope.launch { loadingManga.value = true - refreshManga.await(params.mangaId, onError = { toast(it.message.orEmpty()) }) + refreshManga.await( + params.mangaId, + onError = { + log.warn(it) { "Error when refreshing manga" } + toast(it.message.orEmpty()) + } + ) + loadingManga.value = false } scope.launch { loadingChapters.value = true - refreshChapters.await(params.mangaId, onError = { toast(it.message.orEmpty()) }) + refreshChapters.await( + params.mangaId, + onError = { + log.warn(it) { "Error when refreshing chapters" } + toast(it.message.orEmpty()) + } + ) + loadingChapters.value = false } } @@ -290,10 +323,8 @@ class MangaScreenViewModel } } - fun downloadChapter(index: Int) { - manga.value?.let { manga -> - scope.launch { queueChapterDownload.await(manga, index, onError = { toast(it.message.orEmpty()) }) } - } + fun downloadChapter(chapterId: Long) { + scope.launch { queueChapterDownload.await(chapterId, onError = { toast(it.message.orEmpty()) }) } } fun deleteDownload(id: Long?) { @@ -312,9 +343,9 @@ class MangaScreenViewModel } } - fun stopDownloadingChapter(index: Int) { + fun stopDownloadingChapter(chapterId: Long) { scope.launch { - chapters.value.find { it.chapter.index == index } + chapters.value.find { it.chapter.id == chapterId } ?.stopDownloading(stopChapterDownload) } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt index 9a031e0f..1fe01178 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/ChapterItem.kt @@ -64,8 +64,8 @@ fun ChapterItem( bookmarkChapter: (Long) -> Unit, unBookmarkChapter: (Long) -> Unit, markPreviousAsRead: (Int) -> Unit, - onClickDownload: (Int) -> Unit, - onClickStopDownload: (Int) -> Unit, + onClickDownload: (Long) -> Unit, + onClickStopDownload: (Long) -> Unit, onClickDeleteChapter: (Long) -> Unit, onSelectChapter: (Long) -> Unit, onUnselectChapter: (Long) -> Unit, @@ -155,8 +155,8 @@ fun ChapterItem( ChapterDownloadIcon( chapterDownload, - { onClickDownload(it.index) }, - { onClickStopDownload(it.index) }, + { onClickDownload(it.id) }, + { onClickStopDownload(it.id) }, { onClickDeleteChapter(it.id) }, ) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt index d085e7dd..942bb182 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/manga/components/MangaScreenContent.kt @@ -107,9 +107,9 @@ fun MangaScreenContent( bookmarkChapter: (Long?) -> Unit, unBookmarkChapter: (Long?) -> Unit, markPreviousRead: (Int) -> Unit, - downloadChapter: (Int) -> Unit, + downloadChapter: (Long) -> Unit, deleteDownload: (Long?) -> Unit, - stopDownloadingChapter: (Int) -> Unit, + stopDownloadingChapter: (Long) -> Unit, onSelectChapter: (Long) -> Unit, onUnselectChapter: (Long) -> Unit, selectAll: () -> Unit, diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt index 846e98af..03b77231 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt @@ -574,6 +574,9 @@ fun ReaderImage( } val decode = decodeState.value if (decode != null && decode is ReaderPage.ImageDecodeState.Success) { + LaunchedEffect(Unit) { + println("loaded") + } Image( bitmap = decode.bitmap, modifier = imageModifier, @@ -582,6 +585,9 @@ fun ReaderImage( filterQuality = FilterQuality.High, ) } else { + LaunchedEffect(Unit) { + println("not loaded") + } LoadingScreen( status == ReaderPage.Status.QUEUE || status == ReaderPage.Status.WORKING, loadingModifier.let { modifier -> diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsBackupScreen.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsBackupScreen.kt index c46f27cc..32ab49c6 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsBackupScreen.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsBackupScreen.kt @@ -191,7 +191,9 @@ class SettingsBackupViewModel RestoreState.SUCCESS -> Status.Success RestoreState.FAILURE -> Status.Error RestoreState.RESTORING_CATEGORIES -> Status.InProgress(0.01f) - RestoreState.RESTORING_MANGA -> Status.InProgress((completed.toFloat() / total).coerceIn(0f, 1f)) + RestoreState.RESTORING_SETTINGS -> Status.InProgress(0.02f) + RestoreState.RESTORING_MANGA -> Status.InProgress((completed.toFloat() / total).coerceIn(0f, 0.99f)) + RestoreState.RESTORING_META -> Status.InProgress(1f) RestoreState.UNKNOWN -> Status.Error } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsServerScreen.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsServerScreen.kt index 2a3d9de4..95666216 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsServerScreen.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsServerScreen.kt @@ -191,18 +191,18 @@ class ServerSettings( getSetting = { it.backupTime }, getInput = { SetSettingsInput(backupTime = it) }, ) - val basicAuthEnabled = getServerFlow( - getSetting = { it.basicAuthEnabled }, - getInput = { SetSettingsInput(basicAuthEnabled = it) }, - ) - val basicAuthPassword = getServerFlow( - getSetting = { it.basicAuthPassword }, - getInput = { SetSettingsInput(basicAuthPassword = it) }, - ) - val basicAuthUsername = getServerFlow( - getSetting = { it.basicAuthUsername }, - getInput = { SetSettingsInput(basicAuthUsername = it) }, - ) +// val basicAuthEnabled = getServerFlow( +// getSetting = { it.basicAuthEnabled }, +// getInput = { SetSettingsInput(basicAuthEnabled = it) }, +// ) +// val basicAuthPassword = getServerFlow( +// getSetting = { it.basicAuthPassword }, +// getInput = { SetSettingsInput(basicAuthPassword = it) }, +// ) +// val basicAuthUsername = getServerFlow( +// getSetting = { it.basicAuthUsername }, +// getInput = { SetSettingsInput(basicAuthUsername = it) }, +// ) val debugLogsEnabled = getServerFlow( getSetting = { it.debugLogsEnabled }, getInput = { SetSettingsInput(debugLogsEnabled = it) }, @@ -263,10 +263,10 @@ class ServerSettings( getSetting = { it.globalUpdateInterval.toString() }, getInput = { SetSettingsInput(globalUpdateInterval = it.toDoubleOrNull()?.takeIf { it !in 0.01..5.99 }) }, ) - val gqlDebugLogsEnabled = getServerFlow( - getSetting = { it.gqlDebugLogsEnabled }, - getInput = { SetSettingsInput(gqlDebugLogsEnabled = it) }, - ) +// val gqlDebugLogsEnabled = getServerFlow( +// getSetting = { it.gqlDebugLogsEnabled }, +// getInput = { SetSettingsInput(gqlDebugLogsEnabled = it) }, +// ) val initialOpenInBrowserEnabled = getServerFlow( getSetting = { it.initialOpenInBrowserEnabled }, getInput = { SetSettingsInput(initialOpenInBrowserEnabled = it) }, @@ -756,13 +756,13 @@ fun LazyListScope.ServerSettingsItems( subtitle = stringResource(MR.strings.host_debug_logging_sub), ) } - item { - SwitchPreference( - preference = serverSettings.gqlDebugLogsEnabled, - title = stringResource(MR.strings.graphql_debug_logs), - subtitle = stringResource(MR.strings.graphql_debug_logs_sub), - ) - } +// item { +// SwitchPreference( +// preference = serverSettings.gqlDebugLogsEnabled, +// title = stringResource(MR.strings.graphql_debug_logs), +// subtitle = stringResource(MR.strings.graphql_debug_logs_sub), +// ) +// } item { SwitchPreference( preference = serverSettings.systemTrayEnabled, @@ -811,32 +811,32 @@ fun LazyListScope.ServerSettingsItems( ) } - item { - SwitchPreference( - preference = serverSettings.basicAuthEnabled, - title = stringResource(MR.strings.basic_auth), - subtitle = stringResource(MR.strings.host_basic_auth_sub), - enabled = !hosted, - ) - } - - item { - val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState() - EditTextPreference( - preference = serverSettings.basicAuthUsername, - title = stringResource(MR.strings.host_basic_auth_username), - enabled = basicAuthEnabledValue && !hosted, - ) - } - item { - val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState() - EditTextPreference( - preference = serverSettings.basicAuthPassword, - title = stringResource(MR.strings.host_basic_auth_password), - visualTransformation = PasswordVisualTransformation(), - enabled = basicAuthEnabledValue && !hosted, - ) - } +// item { +// SwitchPreference( +// preference = serverSettings.basicAuthEnabled, +// title = stringResource(MR.strings.basic_auth), +// subtitle = stringResource(MR.strings.host_basic_auth_sub), +// enabled = !hosted, +// ) +// } +// +// item { +// val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState() +// EditTextPreference( +// preference = serverSettings.basicAuthUsername, +// title = stringResource(MR.strings.host_basic_auth_username), +// enabled = basicAuthEnabledValue && !hosted, +// ) +// } +// item { +// val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState() +// EditTextPreference( +// preference = serverSettings.basicAuthPassword, +// title = stringResource(MR.strings.host_basic_auth_password), +// visualTransformation = PasswordVisualTransformation(), +// enabled = basicAuthEnabledValue && !hosted, +// ) +// } item { SwitchPreference( preference = serverSettings.flareSolverrEnabled, diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreen.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreen.kt index ddbf094c..6cad7bdc 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreen.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreen.kt @@ -7,18 +7,40 @@ package ca.gosyer.jui.ui.sources.browse import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import ca.gosyer.jui.domain.source.model.Source +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter import ca.gosyer.jui.ui.base.screen.BaseScreen import ca.gosyer.jui.ui.manga.MangaScreen import ca.gosyer.jui.ui.sources.browse.components.SourceScreenContent import ca.gosyer.jui.ui.sources.browse.filter.SourceFiltersViewModel +import ca.gosyer.jui.ui.sources.browse.filter.model.SourceFiltersView import ca.gosyer.jui.ui.sources.components.LocalSourcesNavigator import ca.gosyer.jui.ui.sources.settings.SourceSettingsScreen import ca.gosyer.jui.ui.stateViewModel import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge +import org.lighthousegames.logging.logging + +private fun SourceFiltersView<*, *>.toSourceFilter(): SourceFilter { + return when (this) { + is SourceFiltersView.CheckBox -> filter.copy(value = state.value) + is SourceFiltersView.Group -> filter.copy(value = state.value.map { it.toSourceFilter() }) + is SourceFiltersView.Header -> filter + is SourceFiltersView.Select -> filter.copy(value = state.value) + is SourceFiltersView.Separator -> filter + is SourceFiltersView.Sort -> filter.copy(value = state.value) + is SourceFiltersView.Text -> filter.copy(value = state.value) + is SourceFiltersView.TriState -> filter.copy(value = state.value) + } +} + +val logs = logging() class SourceScreen( val source: Source, @@ -34,6 +56,21 @@ class SourceScreen( val filterVM = stateViewModel { sourceFiltersViewModel(it, SourceFiltersViewModel.Params(source.id)) } + LaunchedEffect(filterVM) { + filterVM.filters.collect { filters -> + filters.map { + if (it is SourceFiltersView.Group) { + it.state.value.map { it.state } + } else { + listOf(it.state) + } + }.flatten().merge() + .mapLatest { + sourceVM.updateFilters(filters.map { it.toSourceFilter() }) + } + .collect() + } + } val sourcesNavigator = LocalSourcesNavigator.current val navigator = LocalNavigator.currentOrThrow SourceScreenContent( diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreenViewModel.kt index da221237..aaa2cf0d 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/SourceScreenViewModel.kt @@ -15,6 +15,7 @@ import ca.gosyer.jui.domain.source.interactor.GetSearchManga import ca.gosyer.jui.domain.source.interactor.SourcePager import ca.gosyer.jui.domain.source.model.MangaPage import ca.gosyer.jui.domain.source.model.Source +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter import ca.gosyer.jui.domain.source.service.CatalogPreferences import ca.gosyer.jui.ui.base.state.SavedStateHandle import ca.gosyer.jui.ui.base.state.getStateFlow @@ -76,6 +77,7 @@ class SourceScreenViewModel( val isLatest = _isLatest.asStateFlow() private val _usingFilters by savedStateHandle.getStateFlow { false } + private val filters = MutableStateFlow?>(null) private val _sourceSearchQuery by savedStateHandle.getStateFlow { initialQuery } val sourceSearchQuery = _sourceSearchQuery.asStateFlow() @@ -113,8 +115,9 @@ class SourceScreenViewModel( { page -> getSearchManga.await( sourceId = source.id, - searchTerm = _query.value, page = page, + searchTerm = _query.value, + filters = filters.value, onError = { toast(it.message.orEmpty()) }, ) } @@ -163,6 +166,10 @@ class SourceScreenViewModel( _sourceSearchQuery.value = query } + fun updateFilters(filters: List) { + this.filters.value = filters + } + fun submitSearch() { startSearch(sourceSearchQuery.value) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersMenu.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersMenu.kt index e7af7198..3e2ee6ea 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersMenu.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersMenu.kt @@ -58,7 +58,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed -import ca.gosyer.jui.domain.source.model.sourcefilters.SortFilter +import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.ui.base.prefs.ExpandablePreference import ca.gosyer.jui.ui.sources.browse.filter.model.SourceFiltersView @@ -224,7 +224,7 @@ fun SelectView(select: SourceFiltersView.Select) { Spinner( modifier = Modifier.weight(1f), // TODO: 2022-05-06 Remove it.values when we hit server version 0.7.0 - items = select.filter.let { it.displayValues ?: it.values.map(Any::toString) }, + items = select.filter.values, selectedItemIndex = state, onSelectItem = select::updateState, ) @@ -297,13 +297,13 @@ fun SortView( asc = state?.ascending ?: false, ) { sort.updateState( - value = SortFilter.Selection( - index, + value = SourceFilter.Sort.SelectionChange( if (state?.index == index) { state?.ascending?.not() ?: false } else { false }, + index, ), ) } @@ -356,18 +356,18 @@ fun TriStateView(triState: SourceFiltersView.TriState) { onClick = { triState.updateState( when (state) { - 0 -> 1 - 1 -> 2 - else -> 0 + SourceFilter.TriState.TriStateValue.IGNORE -> SourceFilter.TriState.TriStateValue.INCLUDE + SourceFilter.TriState.TriStateValue.INCLUDE -> SourceFilter.TriState.TriStateValue.EXCLUDE + SourceFilter.TriState.TriStateValue.EXCLUDE -> SourceFilter.TriState.TriStateValue.IGNORE }, ) }, action = { TriStateCheckbox( state = when (state) { - 1 -> ToggleableState.On - 2 -> ToggleableState.Indeterminate - else -> ToggleableState.Off + SourceFilter.TriState.TriStateValue.INCLUDE -> ToggleableState.On + SourceFilter.TriState.TriStateValue.EXCLUDE -> ToggleableState.Indeterminate + SourceFilter.TriState.TriStateValue.IGNORE -> ToggleableState.Off }, onClick = null, ) diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersViewModel.kt index 6196a07d..3f1836b8 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/SourceFiltersViewModel.kt @@ -7,7 +7,6 @@ package ca.gosyer.jui.ui.sources.browse.filter import ca.gosyer.jui.domain.source.interactor.GetFilterList -import ca.gosyer.jui.domain.source.interactor.SetSourceFilter import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter import ca.gosyer.jui.ui.base.state.SavedStateHandle import ca.gosyer.jui.ui.base.state.getStateFlow @@ -20,12 +19,9 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.supervisorScope import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject import org.lighthousegames.logging.logging @@ -33,21 +29,18 @@ import org.lighthousegames.logging.logging class SourceFiltersViewModel( private val sourceId: Long, private val getFilterList: GetFilterList, - private val setSourceFilter: SetSourceFilter, contextWrapper: ContextWrapper, - private val savedStateHandle: SavedStateHandle, + savedStateHandle: SavedStateHandle, ) : ViewModel(contextWrapper) { @Inject constructor( getFilterList: GetFilterList, - setSourceFilter: SetSourceFilter, contextWrapper: ContextWrapper, @Assisted savedStateHandle: SavedStateHandle, @Assisted params: Params, ) : this( params.sourceId, getFilterList, - setSourceFilter, contextWrapper, savedStateHandle, ) @@ -65,44 +58,10 @@ class SourceFiltersViewModel( val filterButtonEnabled = _filterButtonEnabled.asStateFlow() init { - getFilters(initialLoad = savedStateHandle["initialLoad"] ?: true) - savedStateHandle["initialLoad"] = false + getFilters() filters.mapLatest { settings -> _filterButtonEnabled.value = settings.isNotEmpty() - supervisorScope { - settings.forEach { filter -> - if (filter is SourceFiltersView.Group) { - filter.state.value.forEach { childFilter -> - childFilter.state.drop(1) - .filterNotNull() - .onEach { - setSourceFilter.await( - sourceId = sourceId, - filterIndex = filter.index, - childFilterIndex = childFilter.index, - filter = it, - onError = { toast(it.message.orEmpty()) }, - ) - getFilters() - } - .launchIn(this) - } - } else { - filter.state.drop(1).filterNotNull() - .onEach { - setSourceFilter.await( - sourceId = sourceId, - filterIndex = filter.index, - filter = it, - onError = { toast(it.message.orEmpty()) }, - ) - getFilters() - } - .launchIn(this) - } - } - } } .catch { log.warn(it) { "Error with filters" } @@ -114,8 +73,8 @@ class SourceFiltersViewModel( _showingFilters.value = show } - private fun getFilters(initialLoad: Boolean = false) { - getFilterList.asFlow(sourceId, reset = initialLoad) + private fun getFilters() { + getFilterList.asFlow(sourceId) .onEach { _filters.value = it.toView() _loading.value = false @@ -129,7 +88,7 @@ class SourceFiltersViewModel( } fun resetFilters() { - getFilters(initialLoad = true) + getFilters() } data class Params( @@ -137,8 +96,8 @@ class SourceFiltersViewModel( ) private fun List.toView() = - mapIndexed { index, sourcePreference -> - SourceFiltersView(index, sourcePreference) + mapIndexed { index, sourceFilters -> + SourceFiltersView(index, sourceFilters) }.toImmutableList() private companion object { diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/model/SourceFiltersView.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/model/SourceFiltersView.kt index 5805d31b..f5afce76 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/model/SourceFiltersView.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/browse/filter/model/SourceFiltersView.kt @@ -6,20 +6,12 @@ package ca.gosyer.jui.ui.sources.browse.filter.model -import ca.gosyer.jui.domain.source.model.sourcefilters.CheckBoxFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.GroupFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.HeaderFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.SelectFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.SeparatorFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.SortFilter import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.TextFilter -import ca.gosyer.jui.domain.source.model.sourcefilters.TriStateFilter import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -sealed class SourceFiltersView { +sealed class SourceFiltersView { abstract val index: Int abstract val name: String abstract val state: StateFlow @@ -31,134 +23,134 @@ sealed class SourceFiltersView { data class CheckBox internal constructor( override val index: Int, override val name: String, - override val filter: CheckBoxFilter.CheckBoxProps, - private val _state: MutableStateFlow = MutableStateFlow(filter.state), - ) : SourceFiltersView() { + override val filter: SourceFilter.Checkbox, + private val _state: MutableStateFlow = MutableStateFlow(filter.value), + ) : SourceFiltersView() { override val state: StateFlow = _state.asStateFlow() override fun updateState(value: Boolean) { _state.value = value } - internal constructor(index: Int, filter: CheckBoxFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Checkbox) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Header internal constructor( override val index: Int, override val name: String, - override val filter: HeaderFilter.HeaderProps, - ) : SourceFiltersView() { - override val state: StateFlow = MutableStateFlow(filter.state).asStateFlow() + override val filter: SourceFilter.Header, + ) : SourceFiltersView() { + override val state: StateFlow = MutableStateFlow(filter.name).asStateFlow() override fun updateState(value: Any) { // NO-OP } - internal constructor(index: Int, filter: HeaderFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Header) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Separator internal constructor( override val index: Int, override val name: String, - override val filter: SeparatorFilter.SeparatorProps, - ) : SourceFiltersView() { - override val state: StateFlow = MutableStateFlow(filter.state).asStateFlow() + override val filter: SourceFilter.Separator, + ) : SourceFiltersView() { + override val state: StateFlow = MutableStateFlow(filter.name).asStateFlow() override fun updateState(value: Any) { // NO-OP } - internal constructor(index: Int, filter: SeparatorFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Separator) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Text internal constructor( override val index: Int, override val name: String, - override val filter: TextFilter.TextProps, - ) : SourceFiltersView() { - private val _state = MutableStateFlow(filter.state) + override val filter: SourceFilter.Text, + ) : SourceFiltersView() { + private val _state = MutableStateFlow(filter.value) override val state: StateFlow = _state.asStateFlow() override fun updateState(value: String) { _state.value = value } - internal constructor(index: Int, preference: TextFilter) : this( + internal constructor(index: Int, preference: SourceFilter.Text) : this( index, - preference.filter.name, - preference.filter, + preference.name, + preference, ) } data class TriState internal constructor( override val index: Int, override val name: String, - override val filter: TriStateFilter.TriStateProps, - private val _state: MutableStateFlow = MutableStateFlow(filter.state), - ) : SourceFiltersView() { - override val state: StateFlow = _state.asStateFlow() + override val filter: SourceFilter.TriState, + private val _state: MutableStateFlow = MutableStateFlow(filter.value), + ) : SourceFiltersView() { + override val state: StateFlow = _state.asStateFlow() - override fun updateState(value: Int) { + override fun updateState(value: SourceFilter.TriState.TriStateValue) { _state.value = value } - internal constructor(index: Int, filter: TriStateFilter) : this( + internal constructor(index: Int, filter: SourceFilter.TriState) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Select internal constructor( override val index: Int, override val name: String, - override val filter: SelectFilter.SelectProps, - private val _state: MutableStateFlow = MutableStateFlow(filter.state), - ) : SourceFiltersView() { + override val filter: SourceFilter.Select, + private val _state: MutableStateFlow = MutableStateFlow(filter.value), + ) : SourceFiltersView() { override val state: StateFlow = _state.asStateFlow() override fun updateState(value: Int) { _state.value = value } - internal constructor(index: Int, filter: SelectFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Select) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Sort internal constructor( override val index: Int, override val name: String, - override val filter: SortFilter.SortProps, - private val _state: MutableStateFlow = MutableStateFlow(filter.state), - ) : SourceFiltersView() { - override val state: StateFlow = _state.asStateFlow() + override val filter: SourceFilter.Sort, + private val _state: MutableStateFlow = MutableStateFlow(filter.value), + ) : SourceFiltersView() { + override val state: StateFlow = _state.asStateFlow() - override fun updateState(value: SortFilter.Selection?) { + override fun updateState(value: SourceFilter.Sort.SelectionChange?) { _state.value = value } - internal constructor(index: Int, filter: SortFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Sort) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } data class Group internal constructor( override val index: Int, override val name: String, - override val filter: GroupFilter.GroupProps, - ) : SourceFiltersView>>() { + override val filter: SourceFilter.Group, + ) : SourceFiltersView>>() { override val state: StateFlow>> = MutableStateFlow( - filter.state.mapIndexed { itemIndex, sourceFilter -> + filter.value.mapIndexed { itemIndex, sourceFilter -> SourceFiltersView(itemIndex, sourceFilter) }, ).asStateFlow() @@ -166,10 +158,10 @@ sealed class SourceFiltersView { override fun updateState(value: List>) { // NO-OP } - internal constructor(index: Int, filter: GroupFilter) : this( + internal constructor(index: Int, filter: SourceFilter.Group) : this( index, - filter.filter.name, - filter.filter, + filter.name, + filter, ) } } @@ -179,12 +171,12 @@ fun SourceFiltersView( sourceFilter: SourceFilter, ): SourceFiltersView<*, *> = when (sourceFilter) { - is CheckBoxFilter -> SourceFiltersView.CheckBox(index, sourceFilter) - is HeaderFilter -> SourceFiltersView.Header(index, sourceFilter) - is SeparatorFilter -> SourceFiltersView.Separator(index, sourceFilter) - is TextFilter -> SourceFiltersView.Text(index, sourceFilter) - is TriStateFilter -> SourceFiltersView.TriState(index, sourceFilter) - is SelectFilter -> SourceFiltersView.Select(index, sourceFilter) - is SortFilter -> SourceFiltersView.Sort(index, sourceFilter) - is GroupFilter -> SourceFiltersView.Group(index, sourceFilter) + is SourceFilter.Checkbox -> SourceFiltersView.CheckBox(index, sourceFilter) + is SourceFilter.Header -> SourceFiltersView.Header(index, sourceFilter) + is SourceFilter.Separator -> SourceFiltersView.Separator(index, sourceFilter) + is SourceFilter.Text -> SourceFiltersView.Text(index, sourceFilter) + is SourceFilter.TriState -> SourceFiltersView.TriState(index, sourceFilter) + is SourceFilter.Select -> SourceFiltersView.Select(index, sourceFilter) + is SourceFilter.Sort -> SourceFiltersView.Sort(index, sourceFilter) + is SourceFilter.Group -> SourceFiltersView.Group(index, sourceFilter) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/globalsearch/GlobalSearchViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/globalsearch/GlobalSearchViewModel.kt index 6ecc77b8..59f1d34a 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/globalsearch/GlobalSearchViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/globalsearch/GlobalSearchViewModel.kt @@ -111,7 +111,7 @@ class GlobalSearchViewModel sources.map { source -> async { semaphore.withPermit { - getSearchManga.asFlow(source, query, 1) + getSearchManga.asFlow(source, 1, query, null) .map { if (it.mangaList.isEmpty()) { Search.Failure(MR.strings.no_results_found.toPlatformString()) diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/SourceSettingsScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/SourceSettingsScreenViewModel.kt index 0356ad9b..1c41f1d6 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/SourceSettingsScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/SourceSettingsScreenViewModel.kt @@ -52,8 +52,7 @@ class SourceSettingsScreenViewModel .onEach { setSourceSetting.await( sourceId = params.sourceId, - settingIndex = setting.index, - setting = it, + setting.props, onError = { toast(it.message.orEmpty()) }, ) getSourceSettings() diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/components/SourceSettingsScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/components/SourceSettingsScreenContent.kt index 0443013f..14ac7cbe 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/components/SourceSettingsScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/components/SourceSettingsScreenContent.kt @@ -44,7 +44,6 @@ import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.EditText import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.List import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.MultiSelect import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.Switch -import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.TwoState import ca.gosyer.jui.uicore.components.VerticalScrollbar import ca.gosyer.jui.uicore.components.keyboardHandler import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter @@ -84,7 +83,7 @@ fun SourceSettingsScreenContent(settings: ImmutableList items(settings, { it.props.hashCode() }) { when (it) { is CheckBox, is Switch -> { - TwoStatePreference(it as TwoState, it is CheckBox) + TwoStatePreference(it, it is CheckBox) } is List -> { @@ -120,7 +119,7 @@ fun SourceSettingsScreenContent(settings: ImmutableList @Composable private fun TwoStatePreference( - twoState: TwoState, + twoState: SourceSettingsView<*, Boolean>, checkbox: Boolean, ) { val state by twoState.state.collectAsState() diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/model/SourceSettingsView.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/model/SourceSettingsView.kt index f99852be..a0454625 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/model/SourceSettingsView.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/settings/model/SourceSettingsView.kt @@ -6,13 +6,17 @@ package ca.gosyer.jui.ui.sources.settings.model -import ca.gosyer.jui.domain.source.model.sourcepreference.CheckBoxPreference -import ca.gosyer.jui.domain.source.model.sourcepreference.EditTextPreference -import ca.gosyer.jui.domain.source.model.sourcepreference.ListPreference -import ca.gosyer.jui.domain.source.model.sourcepreference.MultiSelectListPreference +import ca.gosyer.jui.domain.source.model.sourcepreference.CheckBoxSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.EditTextSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.ListSourcePreference +import ca.gosyer.jui.domain.source.model.sourcepreference.MultiSelectListSourcePreference import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference -import ca.gosyer.jui.domain.source.model.sourcepreference.SwitchPreference -import ca.gosyer.jui.domain.source.model.sourcepreference.TwoStateProps +import ca.gosyer.jui.domain.source.model.sourcepreference.SwitchSourcePreference +import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.CheckBox +import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.EditText +import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.List +import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.MultiSelect +import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView.Switch import ca.gosyer.jui.ui.util.lang.stringFormat import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.minus @@ -23,7 +27,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -sealed class SourceSettingsView { +sealed class SourceSettingsView { abstract val index: Int abstract val title: String? abstract val subtitle: String? @@ -36,32 +40,26 @@ sealed class SourceSettingsView { open val summary: String? get() = subtitle?.let { withFormat(it, state.value) } - sealed class TwoState( - props: TwoStateProps, - private val _state: MutableStateFlow = MutableStateFlow( - props.currentValue - ?: props.defaultValue - ?: false, - ), - ) : SourceSettingsView() { + data class CheckBox internal constructor( + override val index: Int, + override val title: String?, + override val subtitle: String?, + override val props: CheckBoxSourcePreference, + ) : SourceSettingsView(){ + private val _state = MutableStateFlow( + props.currentValue ?: props.default, + ) override val state: StateFlow = _state.asStateFlow() override fun updateState(value: Boolean) { _state.value = value } - } - data class CheckBox internal constructor( - override val index: Int, - override val title: String?, - override val subtitle: String?, - override val props: TwoStateProps, - ) : TwoState(props) { - internal constructor(index: Int, preference: CheckBoxPreference) : this( + internal constructor(index: Int, preference: CheckBoxSourcePreference) : this( index, - preference.props.title, - preference.props.summary, - preference.props, + preference.title, + preference.summary, + preference, ) } @@ -69,13 +67,22 @@ sealed class SourceSettingsView { override val index: Int, override val title: String?, override val subtitle: String?, - override val props: TwoStateProps, - ) : TwoState(props) { - internal constructor(index: Int, preference: SwitchPreference) : this( + override val props: SwitchSourcePreference, + ) : SourceSettingsView() { + private val _state = MutableStateFlow( + props.currentValue ?: props.default, + ) + override val state: StateFlow = _state.asStateFlow() + + override fun updateState(value: Boolean) { + _state.value = value + } + + internal constructor(index: Int, preference: SwitchSourcePreference) : this( index, - preference.props.title, - preference.props.summary, - preference.props, + preference.title, + preference.summary, + preference, ) } @@ -83,21 +90,21 @@ sealed class SourceSettingsView { override val index: Int, override val title: String?, override val subtitle: String?, - override val props: ListPreference.ListProps, - ) : SourceSettingsView() { + override val props: ListSourcePreference, + ) : SourceSettingsView() { private val _state = MutableStateFlow( - props.currentValue ?: props.defaultValue ?: "0", + props.currentValue ?: props.default ?: "0", ) override val state: StateFlow = _state.asStateFlow() override fun updateState(value: String) { _state.value = value } - internal constructor(index: Int, preference: ListPreference) : this( + internal constructor(index: Int, preference: ListSourcePreference) : this( index, - preference.props.title, - preference.props.summary, - preference.props, + preference.title, + preference.summary, + preference, ) override val summary: String? @@ -113,21 +120,21 @@ sealed class SourceSettingsView { override val index: Int, override val title: String?, override val subtitle: String?, - override val props: MultiSelectListPreference.MultiSelectListProps, - ) : SourceSettingsView?>() { + override val props: MultiSelectListSourcePreference, + ) : SourceSettingsView?>() { private val _state = MutableStateFlow( - props.currentValue?.toImmutableList() ?: props.defaultValue?.toImmutableList(), + props.currentValue?.toImmutableList() ?: props.default?.toImmutableList(), ) override val state: StateFlow?> = _state.asStateFlow() override fun updateState(value: ImmutableList?) { _state.value = value } - internal constructor(index: Int, preference: MultiSelectListPreference) : this( + internal constructor(index: Int, preference: MultiSelectListSourcePreference) : this( index, - preference.props.title, - preference.props.summary, - preference.props, + preference.title, + preference.summary, + preference, ) fun getOptions() = @@ -150,21 +157,21 @@ sealed class SourceSettingsView { override val subtitle: String?, val dialogTitle: String?, val dialogMessage: String?, - override val props: EditTextPreference.EditTextProps, - ) : SourceSettingsView() { - private val _state = MutableStateFlow(props.currentValue ?: props.defaultValue.orEmpty()) + override val props: EditTextSourcePreference, + ) : SourceSettingsView() { + private val _state = MutableStateFlow(props.currentValue ?: props.default.orEmpty()) override val state: StateFlow = _state.asStateFlow() override fun updateState(value: String) { _state.value = value } - internal constructor(index: Int, preference: EditTextPreference) : this( + internal constructor(index: Int, preference: EditTextSourcePreference) : this( index, - preference.props.title, - preference.props.summary, - preference.props.dialogTitle, - preference.props.dialogMessage, - preference.props, + preference.title, + preference.summary, + preference.dialogTitle, + preference.dialogMessage, + preference, ) } } @@ -179,9 +186,9 @@ fun SourceSettingsView( preference: SourcePreference, ): SourceSettingsView<*, *> = when (preference) { - is CheckBoxPreference -> SourceSettingsView.CheckBox(index, preference) - is SwitchPreference -> SourceSettingsView.Switch(index, preference) - is ListPreference -> SourceSettingsView.List(index, preference) - is MultiSelectListPreference -> SourceSettingsView.MultiSelect(index, preference) - is EditTextPreference -> SourceSettingsView.EditText(index, preference) + is CheckBoxSourcePreference -> CheckBox(index, preference) + is SwitchSourcePreference -> Switch(index, preference) + is EditTextSourcePreference -> EditText(index, preference) + is ListSourcePreference -> List(index, preference) + is MultiSelectListSourcePreference -> MultiSelect(index, preference) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt index 5d82e035..46bec1eb 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt @@ -150,7 +150,7 @@ class UpdatesScreenViewModel _selectedIds.value = persistentListOf() return@launch } - queueChapterDownload.await(chapter, onError = { toast(it.message.orEmpty()) }) + queueChapterDownload.await(chapter.id, onError = { toast(it.message.orEmpty()) }) } }