mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-26 17:35:52 +02:00
Compare commits
410 Commits
Author | SHA1 | Date | |
---|---|---|---|
c8d68590db | |||
94448faf97 | |||
28028c789c | |||
f8834ee764 | |||
7c703b17d3 | |||
f77ade7dda | |||
91712daee8 | |||
c615f4d458 | |||
9e09a20e65 | |||
7115a9b9fe | |||
fd8b97fc87 | |||
4dd67e4348 | |||
10973bf3cd | |||
934ed0551a | |||
38428c6ebe | |||
bf85e147e7 | |||
d2dd34c2e5 | |||
c4ab2b4675 | |||
aa2ec5940f | |||
79323de326 | |||
08e6487a9a | |||
4498b10a10 | |||
6f2bb18d72 | |||
b690de55e5 | |||
83fda20078 | |||
f656a37045 | |||
c58b495433 | |||
242aeb6a68 | |||
d9969cea8a | |||
d61db5931e | |||
0ea3ac9807 | |||
f9e43f574f | |||
5ef11e61d0 | |||
48546c3db4 | |||
4d87ed496c | |||
06d12e6562 | |||
477e3d9b94 | |||
3c16082636 | |||
29aee68ec7 | |||
75e23299b4 | |||
935ff1ee98 | |||
c672cb81ec | |||
7559c133c0 | |||
589bdba0b1 | |||
aca65f13bb | |||
7bf30a094a | |||
5454279a8e | |||
006bcdf934 | |||
b00f00730d | |||
f2c48480b6 | |||
1730dd6af1 | |||
2501fef9e4 | |||
12e41b6e6f | |||
c892c793a8 | |||
3a82b4d924 | |||
b4b3a4d286 | |||
448702e5be | |||
2ef1f07aae | |||
1a319601de | |||
cdf242e8c8 | |||
aee785a8bb | |||
d45fc1e245 | |||
14500ba4f8 | |||
345e9c2a9a | |||
b53e24e0db | |||
d3a73fc228 | |||
c2812fca24 | |||
856847a60a | |||
748e2480d3 | |||
2ebc8d9ae5 | |||
e28b015580 | |||
e4bc8990fb | |||
a179327d9d | |||
823749fc1e | |||
2b5d9fd76b | |||
7a972dfdb7 | |||
c31e75f02f | |||
b56b8b55b4 | |||
2695a4d8c7 | |||
1a4dad72a9 | |||
b7e6b4c28a | |||
dc2d470413 | |||
293b967858 | |||
c637172ee0 | |||
e468554fd9 | |||
5b5eb92184 | |||
58ebf14691 | |||
992bab4f79 | |||
6fe650319d | |||
f301dc64f0 | |||
33a2219716 | |||
62480f090b | |||
e7937fe562 | |||
287489d7d0 | |||
2df0236669 | |||
c54d77333f | |||
8c494f314c | |||
8cea78de83 | |||
b6468c7e31 | |||
1967923a94 | |||
91004ad514 | |||
a2ee4e63ae | |||
4d8289cd36 | |||
289264878e | |||
768bb7b503 | |||
db4ae134aa | |||
7329f03bc5 | |||
82ea643c7d | |||
741c10e0b9 | |||
34bb90f3c2 | |||
f04cf72c0c | |||
157438e0c1 | |||
75b23c99ec | |||
6bb3070c57 | |||
7df10b076c | |||
2245658363 | |||
46774771ec | |||
6263817bb4 | |||
60456fe0e9 | |||
a0f47d3f1b | |||
6efcb8ccfa | |||
0d128b75e2 | |||
0067d474c8 | |||
cf393b217b | |||
e265b929a1 | |||
4cd01428ed | |||
3be05fbf9b | |||
5d90ba8aa0 | |||
48cab708ce | |||
5d9753d6a7 | |||
425e48bec6 | |||
a42be4a833 | |||
30e030bb8e | |||
2a3c3d8d6a | |||
7b026cec8d | |||
d8b528a4e0 | |||
0f45907144 | |||
c4c9931ae2 | |||
68345e636e | |||
0861c5618c | |||
817418f7c9 | |||
4eb2cd85b2 | |||
086eac5975 | |||
addd6bffbd | |||
1e65313fa7 | |||
c4c6e41c46 | |||
920ca405a2 | |||
6d3a3b3f39 | |||
50d46fe7f6 | |||
91e282d7e5 | |||
a0f10f868e | |||
6a423f0650 | |||
5cc84403e1 | |||
ab61a65b4a | |||
01ec26842d | |||
bbf5817805 | |||
50981cb102 | |||
611ec8103c | |||
12c672667c | |||
db3c98fe72 | |||
f401574f5a | |||
3251fb36c8 | |||
94a410f50f | |||
a14c01c1de | |||
ca3b948628 | |||
a8230ad574 | |||
8e1b5b4803 | |||
8552838bda | |||
46417fe427 | |||
dac04f2929 | |||
63da463e02 | |||
817e144ff6 | |||
9d2d78ae5b | |||
c44db54d9f | |||
376bbeb724 | |||
0e2bdb7863 | |||
235bc77457 | |||
593172f891 | |||
e20c66b156 | |||
5f4825465e | |||
bc6a12a4f7 | |||
90db3acefd | |||
2f2f59279d | |||
4992f87cb1 | |||
7608cb0da3 | |||
9dd9e741f3 | |||
171db639ff | |||
3ede42252c | |||
a94ca175e2 | |||
3749cee28f | |||
ca500da4d8 | |||
820ed6a468 | |||
7cbe18d325 | |||
8937e22ce4 | |||
82a3a98a5a | |||
d97eab0328 | |||
a61e2799db | |||
1009e15aa6 | |||
01c6e46a71 | |||
ed5e013874 | |||
f8e4153dbf | |||
f7a92cf6ac | |||
e748d91d4a | |||
2c4ddca38e | |||
6ca32710be | |||
f05e251991 | |||
a3f3f9d562 | |||
410fcb73c5 | |||
b6d6de6b9f | |||
09cebf20f3 | |||
a8c732d67b | |||
843c9c7e57 | |||
c88b79fa17 | |||
3f9820ac79 | |||
c288e6b8fa | |||
8945ef8880 | |||
99a717f849 | |||
4622b18c99 | |||
4f5270cb7d | |||
719d427956 | |||
d7a21771a5 | |||
be854b3e90 | |||
47f079891f | |||
696dc59ea5 | |||
5f6666a438 | |||
f284a656d7 | |||
1c3d566f8d | |||
373463e995 | |||
7be9b49143 | |||
059a79debb | |||
1a70ebe7ea | |||
beda99bbe0 | |||
bb1e7816e1 | |||
b0dc20e00c | |||
3d66eaea83 | |||
5313a5d5d2 | |||
5b189a909b | |||
75a687138d | |||
ba91b483a0 | |||
3a8b5e1b5e | |||
94d1b68598 | |||
8eda4df71f | |||
8ad9337863 | |||
cd13e187cf | |||
bcc21e55bd | |||
5fbecfd7b7 | |||
3480b45098 | |||
5076ab3049 | |||
44366ac058 | |||
4f2a794fba | |||
fe6aa4358f | |||
f99b62a069 | |||
ac1bed38f9 | |||
217b03a292 | |||
28bceffc6f | |||
09266a155c | |||
3d7591feca | |||
e14909fff4 | |||
fe579c4865 | |||
37118088d4 | |||
5c9e9bd2c4 | |||
db35ba53b1 | |||
758d223776 | |||
21a9bf2463 | |||
a54d9912d0 | |||
7e74949d38 | |||
a8c5780963 | |||
f4ac754d02 | |||
0347d3970a | |||
acc2312384 | |||
7d34ff214c | |||
e2179a6669 | |||
5c37347cec | |||
ef3a6c80a7 | |||
2a2c6cee5f | |||
7dff3cc6cb | |||
8c1171a722 | |||
2c850d0e33 | |||
f1b85ff39d | |||
b7fa25777d | |||
2d86f69caa | |||
e22896a956 | |||
be5802e473 | |||
434c90d378 | |||
eb6ba96b57 | |||
5325e590ec | |||
6ad6dae191 | |||
3f34fa1f58 | |||
d12ea86b55 | |||
a8e45beb51 | |||
ba2a528886 | |||
d60367768b | |||
db6528d3fa | |||
f5873d70c6 | |||
10e349f76e | |||
b1ccebf329 | |||
3407eb84c5 | |||
6017229d1b | |||
4f00af3173 | |||
9da232dcd8 | |||
acd43005df | |||
c31cf2a03a | |||
51c964de3a | |||
dad24e785b | |||
a908283e86 | |||
d8725c7b7f | |||
262f8449b4 | |||
bdf035d60a | |||
0270878748 | |||
6ada3c90ff | |||
4e628fe6de | |||
a8eebd824a | |||
afa0a0a0e2 | |||
92b039fac7 | |||
acc65529a0 | |||
3061f198e9 | |||
6fc1f4fc21 | |||
a0f49b16c5 | |||
c6c4c1c393 | |||
811931ccc0 | |||
08d5633d81 | |||
c76d5dd30c | |||
340357d158 | |||
11ed47397d | |||
6ce54eb845 | |||
d0236aaecf | |||
00059848b4 | |||
e45f6d0c92 | |||
18ccde082d | |||
21bc0f1952 | |||
a37be747e9 | |||
bc3bb82651 | |||
ba00d9e5d2 | |||
bf9edda04c | |||
9c9357639a | |||
3733871d2f | |||
54471a014f | |||
8749be518f | |||
6d880c938a | |||
34aa4eb291 | |||
280b0f42db | |||
65387d0089 | |||
d41c103a72 | |||
0b93b9e059 | |||
ea3f933e95 | |||
b006fe3a22 | |||
37ff3b4920 | |||
1e93d785e5 | |||
999bd4efee | |||
3222247969 | |||
dd6c9ce2fe | |||
7446b28ff1 | |||
38c6702b8f | |||
afcf4b2988 | |||
ebb96a6ff4 | |||
8b0affe9bd | |||
1a25cea0d6 | |||
2ecbcdf4bd | |||
642b392d44 | |||
8dce7b3e9e | |||
33e90d6449 | |||
50b17d5d34 | |||
7818885406 | |||
26af7ccc77 | |||
5d1f79012e | |||
cac80daa71 | |||
fc184f1cfa | |||
725fcbba0e | |||
bdeb209d43 | |||
a078f1ab1b | |||
86c3d8c064 | |||
156191af44 | |||
57bba9e5ab | |||
dd1923fe88 | |||
df773ee15c | |||
f5451a6881 | |||
fcec1581b7 | |||
11cc789e36 | |||
16f9fb2f40 | |||
6bfaa85e84 | |||
8f43fb9530 | |||
04d2a3399b | |||
054bf8ec5d | |||
8417f5a63c | |||
26b46cace0 | |||
0849111247 | |||
f9c25b350e | |||
5b12c144da | |||
f38130d086 | |||
4b60138d41 | |||
fde7bfa3d1 | |||
69635ee66a | |||
224f29077d | |||
e1ab1fdb65 | |||
3e86cb094b | |||
9fbd3fe33f | |||
073e9f94ff | |||
64c0d9506d | |||
f638092ab9 | |||
d0c4463ab3 | |||
ad107860b9 | |||
5efb31bd71 | |||
e4a2f35907 | |||
e49781de7a | |||
37cb4ec0c2 | |||
401134fa8e | |||
87391832ba | |||
e36d31bf0f | |||
37b7efbc87 | |||
6e4a30e593 |
.github
.gitignoreapp
build.gradle.kts
build.gradle.ktssrc
debug
res
mipmap-anydpi-v26
main
AndroidManifest.xmlbaseline-prof.txt
java
eu
kanade
core
data
domain
DomainModule.kt
backup
service
base
category
interactor
chapter
interactor
GetChapter.ktGetChapterByMangaId.ktSetDefaultChapterSettings.ktSetReadStatus.ktSyncChaptersWithSource.ktSyncChaptersWithTrackServiceTwoWay.ktUpdateChapter.kt
model
download
extension
history
interactor
library
service
manga
interactor
GetDuplicateLibraryManga.ktGetFavorites.ktGetLibraryManga.ktGetManga.ktGetMangaWithChapters.ktNetworkToLocalManga.ktResetViewerFlags.ktSetMangaChapterFlags.ktSetMangaViewerFlags.ktUpdateManga.kt
model
source
interactor
GetEnabledSources.ktGetLanguagesWithSources.ktGetSourcesWithFavoriteCount.ktGetSourcesWithNonLibraryManga.ktToggleLanguage.ktToggleSource.ktToggleSourcePin.kt
model
repository
service
track
interactor
model
service
store
ui
updates
interactor
presentation
browse
BrowseBadges.ktBrowseSourceScreen.ktBrowseSourceState.ktExtensionDetailsScreen.ktExtensionDetailsState.ktExtensionFilterScreen.ktExtensionFilterState.ktExtensionsScreen.ktExtensionsState.ktGlobalSearchScreen.ktMigrateMangaScreen.ktMigrateMangaState.ktMigrateSearchScreen.ktMigrateSourceScreen.ktMigrateSourceState.ktSourceSearchScreen.ktSourcesFilterScreen.ktSourcesFilterState.ktSourcesScreen.ktSourcesState.kt
components
category
components
AdaptiveSheet.ktAlertDialog.ktAppBar.ktBadges.ktBanners.ktChangeCategoryDialog.ktChapterDownloadIndicator.ktCommonMangaItem.ktDeleteLibraryMangaDialog.ktDivider.ktDownloadDropdownMenu.ktDropdownMenu.ktDuplicateMangaDialog.ktEmptyScreen.ktInfoScaffold.ktListGroupHeader.ktLoadingScreen.ktMangaBottomActionMenu.ktMangaCover.ktNavigationBar.ktNavigationRail.ktPager.ktPill.ktPreferences.ktPullRefresh.ktRelativeDateHeader.ktScaffold.ktSettingsItems.ktSwipeRefresh.ktTabbedDialog.ktTabbedScreen.ktTabs.ktTrackLogoIcon.ktTwoPanelBox.ktVerticalFastScroller.kt
crash
history
library
manga
ChapterSettingsDialog.ktMangaScreen.ktTrackInfoDialogHome.ktTrackInfoDialogSelector.ktTrackServiceSearch.kt
components
more
MoreScreen.ktNewUpdateScreen.kt
settings
PreferenceItem.ktPreferenceModel.ktPreferenceScaffold.kt
screen
AboutScreen.ktClearDatabaseScreen.ktCommons.ktLicensesScreen.ktSettingsAdvancedScreen.ktSettingsAppearanceScreen.ktSettingsBackupScreen.ktSettingsBrowseScreen.ktSettingsDownloadScreen.ktSettingsGeneralScreen.ktSettingsLibraryScreen.ktSettingsMainScreen.ktSettingsReaderScreen.ktSettingsSearchScreen.ktSettingsSecurityScreen.ktSettingsTrackingScreen.ktWorkerInfoScreen.kt
widget
stats
theme
updates
util
webview
tachiyomi
App.ktAppModule.ktMigrations.kt
crash
data
backup
BackupCreatorJob.ktBackupFileValidator.ktBackupManager.ktBackupRestoreService.ktBackupRestorer.kt
models
cache
coil
database
download
DownloadCache.ktDownloadManager.ktDownloadNotifier.ktDownloadPendingDeleter.ktDownloadProvider.ktDownloadService.ktDownloadStore.ktDownloader.kt
model
library
notification
saver
track
EnhancedTrackService.ktNoLoginTrackService.ktTrackManager.ktTrackService.kt
anilist
bangumi
job
kavita
kitsu
komga
mangaupdates
myanimelist
shikimori
suwayomi
updater
extension
glance
source
ui
base
activity
controller
BaseController.ktComposeController.ktConductorExtensions.ktDialogController.ktNucleusController.ktOneWayFadeChangeHandler.ktRootController.ktSearchableNucleusController.kt
presenter
browse
BrowseController.ktBrowsePresenter.ktBrowseTab.kt
extension
ExtensionFilterController.ktExtensionFilterPresenter.ktExtensionFilterScreen.ktExtensionFilterScreenModel.ktExtensionsScreenModel.ktExtensionsTab.kt
details
migration
MigrationFlags.kt
manga
MigrateMangaPresenter.ktMigrationMangaController.ktMigrationMangaScreen.ktMigrationMangaScreenModel.kt
search
MigrateDialog.ktMigrateSearchScreen.ktMigrateSearchScreenModel.ktSearchController.ktSearchPresenter.ktSourceSearchController.ktSourceSearchScreen.kt
sources
source
category
download
DownloadAdapter.ktDownloadController.ktDownloadItem.ktDownloadPresenter.ktDownloadQueueScreen.ktDownloadQueueScreenModel.kt
history
home
library
LibraryController.ktLibraryItem.ktLibraryPresenter.ktLibraryScreenModel.ktLibrarySettingsSheet.ktLibraryTab.kt
main
manga
more
reader
ReaderActivity.ktReaderPageSheet.ktReaderViewModel.ktSaveImageNotifier.kt
loader
ChapterLoader.ktDirectoryPageLoader.ktDownloadPageLoader.ktEpubPageLoader.ktHttpPageLoader.ktPageLoader.ktRarPageLoader.ktZipPageLoader.kt
model
setting
OrientationType.ktReaderColorFilterSettings.ktReaderPreferences.ktReaderReadingModeSettings.ktReaderSettingsSheet.kt
viewer
recent
security
setting
stats
updates
webview
util
BackupUtil.ktCrashLogUtil.ktMangaExtensions.kt
chapter
preference
storage
system
BooleanExtensions.ktContextExtensions.ktImageUtil.ktIntentExtensions.ktLocaleHelper.ktNotificationExtensions.kt
view
widget
AutofitRecyclerView.ktDialogCheckboxView.ktEmptyView.ktMaterialSpinnerView.ktMinMaxNumberPicker.ktTachiyomiAppBarLayout.ktTachiyomiBottomNavigationView.ktTachiyomiChangeHandlerFrameLayout.ktTachiyomiCoordinatorLayout.ktTachiyomiFullscreenDialog.ktTachiyomiScrollingViewBehavior.ktTachiyomiSearchView.kt
res
anim-v33
shared_axis_x_pop_enter.xmlshared_axis_x_pop_exit.xmlshared_axis_x_push_enter.xmlshared_axis_x_push_exit.xml
anim
fade_in_short.xmlfade_out_short.xmlshared_axis_x_pop_enter.xmlshared_axis_x_pop_exit.xmlshared_axis_x_push_enter.xmlshared_axis_x_push_exit.xml
color
drawable-nodpi
drawable
anim_browse_leave.xmlanim_library_leave.xmlanim_more_enter.xmlanim_updates_leave.xmlic_arrow_forward_24dp.xmlic_browse_filled_24dp.xmlic_browse_outline_24dp.xmlic_browse_selector_24dp.xmlic_history_24dp.xmlic_history_selector_24dp.xmlic_library_filled_24dp.xmlic_library_outline_24dp.xmlic_library_selector_24dp.xmlic_more_24dp.xmlic_more_selector_24dp.xmlic_more_vert_24.xmlic_search_24dp.xmlic_updates_filled_24dp.xmlic_updates_outline_24dp.xmlic_updates_selector_24dp.xmlic_webview_24dp.xmllibrary_item_selector.xmlmaterial_popup_background.xmlrounded_rectangle.xml
layout-sw720dp
layout
common_dialog_with_checkbox.xmlglobal_search_controller.xmlglobal_search_controller_card.xmlglobal_search_controller_card_item.xmlmain_activity.xmltrack_chapters_dialog.xmltrack_controller.xmltrack_item.xmltrack_score_dialog.xmltrack_search_dialog.xmltrack_search_item.xml
menu
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
values
buildSrc
core
data
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
src
main
AndroidManifest.xml
java
tachiyomi
data
AndroidDatabaseHandler.ktDatabaseAdapter.ktDatabaseHandler.ktQueryPagingSource.ktTransactionContext.kt
category
chapter
history
manga
source
track
updates
sqldelight
domain
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
gradle.propertiessrc
main
AndroidManifest.xml
java
tachiyomi
domain
category
chapter
interactor
model
repository
history
interactor
model
repository
library
manga
source
track
updates
test
java
tachiyomi
domain
library
model
gradle
gradlewgradlew.bati18n
build.gradle.kts
src
main
res
values-am
values-ar
values-b+es+419
values-be
values-bg
values-bn
values-ca
values-ceb
values-cs
values-cv
values-da
values-de
values-el
values-eo
values-es
values-eu
values-fa
values-fi
values-fil
values-fr
values-gl
values-he
values-hi
values-hr
values-hu
values-in
values-it
values-ja
values-jv
values-ka-rGE
values-kk
values-km
values-kn
values-ko
values-lt
values-lv
values-mr
values-ms
values-my
values-nb-rNO
values-ne
values-nl
values-nn
values-or
values-pl
values-pt-rBR
values-pt
values-ro
values-ru
values-sa
values-sah
values-sc
values-sdh
values-si
values-sk
values-sq
values-sr
values-sv
values-ta
values-te
values-th
values-ti
values-tr
values-uk
values-ur-rPK
values-uz
values-vi
values-zh-rCN
values-zh-rTW
values
macrobenchmark
presentation-core
presentation-widget
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
settings.gradle.ktssrc
main
source-api
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -3,7 +3,7 @@
|
|||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.14.0)
|
- To the latest version of the app (stable is v0.14.7)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: Tachiyomi version
|
label: Tachiyomi version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Tachiyomi version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.14.0"
|
Example: "0.14.7"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -33,7 +33,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@ -5,10 +5,8 @@
|
|||||||
"schedule": ["every sunday"],
|
"schedule": ["every sunday"],
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"androidx.core:core-splashscreen",
|
"androidx.core:core-splashscreen",
|
||||||
"androidx.work:work-runtime-ktx",
|
|
||||||
"info.android15.nucleus:nucleus-support-v7",
|
|
||||||
"info.android15.nucleus:nucleus",
|
|
||||||
"com.android.tools:r8",
|
"com.android.tools:r8",
|
||||||
"com.google.guava:guava"
|
"com.google.guava:guava",
|
||||||
|
"com.github.commandiron:WheelPickerCompose"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
4
.github/workflows/build_pull_request.yml
vendored
4
.github/workflows/build_pull_request.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Dependency Review
|
- name: Dependency Review
|
||||||
uses: actions/dependency-review-action@v2
|
uses: actions/dependency-review-action@v3
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 11
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
@ -36,4 +36,4 @@ jobs:
|
|||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
12
.github/workflows/build_push.yml
vendored
12
.github/workflows/build_push.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
@ -104,3 +104,13 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
update-website:
|
||||||
|
needs: [build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||||
|
steps:
|
||||||
|
- name: Trigger Netlify build hook
|
||||||
|
run: curl -s -X POST -d {} "https://api.netlify.com/build_hooks/${TOKEN}"
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.NETLIFY_HOOK_RELEASE }}
|
||||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v3
|
- uses: dessant/lock-threads@v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: '2'
|
issue-inactive-days: '2'
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -10,4 +10,7 @@
|
|||||||
*/build
|
*/build
|
||||||
/build
|
/build
|
||||||
*.apk
|
*.apk
|
||||||
app/**/output.json
|
app/**/output.json
|
||||||
|
|
||||||
|
# Unnecessary file
|
||||||
|
*.swp
|
@ -1,5 +1,5 @@
|
|||||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@ -7,7 +7,6 @@ plugins {
|
|||||||
kotlin("android")
|
kotlin("android")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
id("com.github.zellius.shortcut-helper")
|
id("com.github.zellius.shortcut-helper")
|
||||||
id("com.squareup.sqldelight")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||||
@ -20,15 +19,11 @@ val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.kanade.tachiyomi"
|
namespace = "eu.kanade.tachiyomi"
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
ndkVersion = AndroidConfig.ndk
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
minSdk = AndroidConfig.minSdk
|
versionCode = 102
|
||||||
targetSdk = AndroidConfig.targetSdk
|
versionName = "0.14.7"
|
||||||
versionCode = 89
|
|
||||||
versionName = "0.14.0"
|
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -59,6 +54,7 @@ android {
|
|||||||
named("debug") {
|
named("debug") {
|
||||||
versionNameSuffix = "-${getCommitCount()}"
|
versionNameSuffix = "-${getCommitCount()}"
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
|
isPseudoLocalesEnabled = true
|
||||||
}
|
}
|
||||||
named("release") {
|
named("release") {
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
@ -99,7 +95,8 @@ android {
|
|||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
create("dev") {
|
create("dev") {
|
||||||
resourceConfigurations.addAll(listOf("en", "xxhdpi"))
|
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||||
|
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,7 +110,6 @@ android {
|
|||||||
"META-INF/README.md",
|
"META-INF/README.md",
|
||||||
"META-INF/NOTICE",
|
"META-INF/NOTICE",
|
||||||
"META-INF/*.kotlin_module",
|
"META-INF/*.kotlin_module",
|
||||||
"META-INF/*.version",
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,57 +135,42 @@ android {
|
|||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
sqldelight {
|
|
||||||
database("Database") {
|
|
||||||
packageName = "eu.kanade.tachiyomi"
|
|
||||||
dialect = "sqlite:3.24"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":i18n"))
|
implementation(project(":i18n"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
implementation(project(":source-api"))
|
implementation(project(":source-api"))
|
||||||
|
implementation(project(":data"))
|
||||||
|
implementation(project(":domain"))
|
||||||
|
implementation(project(":presentation-core"))
|
||||||
|
implementation(project(":presentation-widget"))
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
implementation(platform(compose.bom))
|
implementation(platform(compose.bom))
|
||||||
implementation(compose.activity)
|
implementation(compose.activity)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.material3.core)
|
implementation(compose.material3.core)
|
||||||
implementation(compose.material3.adapter)
|
implementation(compose.material.core)
|
||||||
implementation(compose.material.icons)
|
implementation(compose.material.icons)
|
||||||
implementation(compose.animation)
|
implementation(compose.animation)
|
||||||
implementation(compose.animation.graphics)
|
implementation(compose.animation.graphics)
|
||||||
implementation(compose.ui.tooling)
|
implementation(compose.ui.tooling)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
implementation(compose.accompanist.webview)
|
implementation(compose.accompanist.webview)
|
||||||
implementation(compose.accompanist.swiperefresh)
|
|
||||||
implementation(compose.accompanist.flowlayout)
|
implementation(compose.accompanist.flowlayout)
|
||||||
implementation(compose.accompanist.pager.core)
|
|
||||||
implementation(compose.accompanist.pager.indicators)
|
|
||||||
implementation(compose.accompanist.permissions)
|
implementation(compose.accompanist.permissions)
|
||||||
|
implementation(compose.accompanist.themeadapter)
|
||||||
|
implementation(compose.accompanist.systemuicontroller)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
|
|
||||||
implementation(libs.bundles.sqlite)
|
implementation(libs.bundles.sqlite)
|
||||||
implementation(androidx.sqlite)
|
|
||||||
implementation(libs.sqldelight.android.driver)
|
|
||||||
implementation(libs.sqldelight.coroutines)
|
|
||||||
implementation(libs.sqldelight.android.paging)
|
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
|
||||||
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
implementation(kotlinx.bundles.coroutines)
|
implementation(kotlinx.bundles.coroutines)
|
||||||
|
|
||||||
// AndroidX libraries
|
// AndroidX libraries
|
||||||
@ -202,7 +183,6 @@ dependencies {
|
|||||||
implementation(androidx.splashscreen)
|
implementation(androidx.splashscreen)
|
||||||
implementation(androidx.recyclerview)
|
implementation(androidx.recyclerview)
|
||||||
implementation(androidx.viewpager)
|
implementation(androidx.viewpager)
|
||||||
implementation(androidx.glance)
|
|
||||||
implementation(androidx.profileinstaller)
|
implementation(androidx.profileinstaller)
|
||||||
|
|
||||||
implementation(androidx.bundles.lifecycle)
|
implementation(androidx.bundles.lifecycle)
|
||||||
@ -235,15 +215,11 @@ dependencies {
|
|||||||
// Preferences
|
// Preferences
|
||||||
implementation(libs.preferencektx)
|
implementation(libs.preferencektx)
|
||||||
|
|
||||||
// Model View Presenter
|
|
||||||
implementation(libs.bundles.nucleus)
|
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation(libs.injekt.core)
|
implementation(libs.injekt.core)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
implementation(libs.bundles.coil)
|
implementation(libs.bundles.coil)
|
||||||
|
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
implementation(libs.subsamplingscaleimageview) {
|
||||||
exclude(module = "image-decoder")
|
exclude(module = "image-decoder")
|
||||||
}
|
}
|
||||||
@ -261,17 +237,12 @@ dependencies {
|
|||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.markwon)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.cascade)
|
implementation(libs.cascade)
|
||||||
implementation(libs.numberpicker)
|
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
|
implementation(libs.wheelpicker)
|
||||||
// Conductor
|
implementation(libs.materialmotion.core)
|
||||||
implementation(libs.bundles.conductor)
|
|
||||||
|
|
||||||
// FlowBinding
|
|
||||||
implementation(libs.bundles.flowbinding)
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
@ -298,17 +269,16 @@ androidComponents {
|
|||||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
|
// Only excluding in standard flavor because this breaks
|
||||||
|
// Layout Inspector's Compose tree
|
||||||
|
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
withType<Test> {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withType<org.jmailen.gradle.kotlinter.tasks.LintTask>().configureEach {
|
withType<LintTask>().configureEach {
|
||||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,10 +286,11 @@ tasks {
|
|||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=com.google.accompanist.pager.ExperimentalPagerApi",
|
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
@ -329,11 +300,19 @@ tasks {
|
|||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
preBuild {
|
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
||||||
val ktlintTask = if (System.getenv("GITHUB_BASE_REF") == null) formatKotlin else lintKotlin
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
dependsOn(ktlintTask)
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
|
project.buildDir.absolutePath + "/compose_metrics"
|
||||||
|
)
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
|
project.buildDir.absolutePath + "/compose_metrics"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@android:color/transparent"/>
|
<background android:drawable="@android:color/transparent"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -23,6 +23,7 @@
|
|||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
||||||
|
|
||||||
<!-- Remove permission from Firebase dependency -->
|
<!-- Remove permission from Firebase dependency -->
|
||||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||||
@ -187,7 +188,7 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".glance.UpdatesGridGlanceReceiver"
|
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
||||||
android:enabled="@bool/glance_appwidget_available"
|
android:enabled="@bool/glance_appwidget_available"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/label_recent_updates">
|
android:label="@string/label_recent_updates">
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,10 @@ package eu.kanade.core.prefs
|
|||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import eu.kanade.tachiyomi.core.preference.Preference
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
|
|
||||||
class PreferenceMutableState<T>(
|
class PreferenceMutableState<T>(
|
||||||
private val preference: Preference<T>,
|
private val preference: Preference<T>,
|
||||||
@ -34,3 +34,5 @@ class PreferenceMutableState<T>(
|
|||||||
return { preference.set(it) }
|
return { preference.set(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
||||||
|
148
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
148
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
|
generator: (T?, T?) -> R?,
|
||||||
|
): List<R> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
val newList = mutableListOf<R>()
|
||||||
|
for (i in -1..lastIndex) {
|
||||||
|
val before = getOrNull(i)
|
||||||
|
before?.let { newList.add(it) }
|
||||||
|
val after = getOrNull(i + 1)
|
||||||
|
val separator = generator.invoke(before, after)
|
||||||
|
separator?.let { newList.add(it) }
|
||||||
|
}
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||||
|
*/
|
||||||
|
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
||||||
|
val mutableMap = ConcurrentHashMap<R, V>()
|
||||||
|
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
||||||
|
return mutableMap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
|
if (shouldAdd) {
|
||||||
|
add(value)
|
||||||
|
} else {
|
||||||
|
remove(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (!predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only the non-null results of applying the
|
||||||
|
* given [transform] function to each element in the original collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
||||||
|
contract { callsInPlace(transform) }
|
||||||
|
val destination = ArrayList<R>()
|
||||||
|
fastForEach { element ->
|
||||||
|
transform(element)?.let { destination.add(it) }
|
||||||
|
}
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the original collection into pair of lists,
|
||||||
|
* where *first* list contains elements for which [predicate] yielded `true`,
|
||||||
|
* while *second* list contains elements for which [predicate] yielded `false`.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastPartition(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val first = ArrayList<T>()
|
||||||
|
val second = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
if (predicate(it)) {
|
||||||
|
first.add(it)
|
||||||
|
} else {
|
||||||
|
second.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
var count = size
|
||||||
|
fastForEach { if (predicate(it)) --count }
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements from the given collection
|
||||||
|
* having distinct keys returned by the given [selector] function.
|
||||||
|
*
|
||||||
|
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
||||||
|
* The elements in the resulting list are in the same order as they were in the source collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
||||||
|
contract { callsInPlace(selector) }
|
||||||
|
val set = HashSet<K>()
|
||||||
|
val list = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
val key = selector(it)
|
||||||
|
if (set.add(key)) list.add(it)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
16
app/src/main/java/eu/kanade/core/util/DurationUtils.kt
Normal file
16
app/src/main/java/eu/kanade/core/util/DurationUtils.kt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||||
|
return toComponents { days, hours, minutes, seconds, _ ->
|
||||||
|
buildList(4) {
|
||||||
|
if (days != 0L) add(context.getString(R.string.day_short, days))
|
||||||
|
if (hours != 0) add(context.getString(R.string.hour_short, hours))
|
||||||
|
if (minutes != 0 && (days == 0L || hours == 0)) add(context.getString(R.string.minute_short, minutes))
|
||||||
|
if (seconds != 0 && days == 0L && hours == 0) add(context.getString(R.string.seconds_short, seconds))
|
||||||
|
}.joinToString(" ").ifBlank { fallback }
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.core.util
|
|
||||||
|
|
||||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
|
||||||
generator: (T?, T?) -> R?,
|
|
||||||
): List<R> {
|
|
||||||
if (isEmpty()) return emptyList()
|
|
||||||
val newList = mutableListOf<R>()
|
|
||||||
for (i in -1..lastIndex) {
|
|
||||||
val before = getOrNull(i)
|
|
||||||
before?.let { newList.add(it) }
|
|
||||||
val after = getOrNull(i + 1)
|
|
||||||
val separator = generator.invoke(before, after)
|
|
||||||
separator?.let { newList.add(it) }
|
|
||||||
}
|
|
||||||
return newList
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
class NoResultsException : Exception()
|
|
@ -1,9 +1,8 @@
|
|||||||
package eu.kanade.data.source
|
package eu.kanade.data.source
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.model.SourceData
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
||||||
Source(
|
Source(
|
||||||
@ -18,7 +17,3 @@ val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
|||||||
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
||||||
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceDataMapper: (Long, String, String) -> SourceData = { id, lang, name ->
|
|
||||||
SourceData(id, lang, name)
|
|
||||||
}
|
|
||||||
|
@ -6,8 +6,8 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
|||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
import tachiyomi.core.util.lang.awaitSingle
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
|
||||||
abstract class SourcePagingSource(
|
abstract class SourcePagingSource(
|
||||||
protected val source: CatalogueSource,
|
protected val source: CatalogueSource,
|
||||||
@ -60,3 +60,5 @@ class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(sou
|
|||||||
return source.fetchLatestUpdates(currentPage).awaitSingle()
|
return source.fetchLatestUpdates(currentPage).awaitSingle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoResultsException : Exception()
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package eu.kanade.data.source
|
package eu.kanade.data.source
|
||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||||
import eu.kanade.domain.source.model.SourceWithCount
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
@ -11,6 +8,9 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
|
|
||||||
class SourceRepositoryImpl(
|
class SourceRepositoryImpl(
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
package eu.kanade.domain
|
package eu.kanade.domain
|
||||||
|
|
||||||
import eu.kanade.data.category.CategoryRepositoryImpl
|
|
||||||
import eu.kanade.data.chapter.ChapterRepositoryImpl
|
|
||||||
import eu.kanade.data.history.HistoryRepositoryImpl
|
|
||||||
import eu.kanade.data.manga.MangaRepositoryImpl
|
|
||||||
import eu.kanade.data.source.SourceDataRepositoryImpl
|
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
import eu.kanade.data.source.SourceRepositoryImpl
|
||||||
import eu.kanade.data.track.TrackRepositoryImpl
|
|
||||||
import eu.kanade.data.updates.UpdatesRepositoryImpl
|
|
||||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
||||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
import eu.kanade.domain.category.interactor.DeleteCategory
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
|
||||||
import eu.kanade.domain.category.interactor.RenameCategory
|
import eu.kanade.domain.category.interactor.RenameCategory
|
||||||
import eu.kanade.domain.category.interactor.ReorderCategory
|
import eu.kanade.domain.category.interactor.ReorderCategory
|
||||||
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
||||||
@ -18,27 +10,18 @@ import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
|||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||||
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
||||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
import eu.kanade.domain.category.interactor.UpdateCategory
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
import eu.kanade.domain.history.interactor.GetNextChapters
|
||||||
import eu.kanade.domain.history.interactor.GetHistory
|
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapter
|
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
|
||||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||||
import eu.kanade.domain.manga.interactor.GetLibraryManga
|
import eu.kanade.domain.manga.interactor.GetLibraryManga
|
||||||
@ -49,7 +32,6 @@ import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
|||||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||||
@ -59,14 +41,32 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
|
|||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
import eu.kanade.domain.source.repository.SourceDataRepository
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
import eu.kanade.domain.track.interactor.DeleteTrack
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.GetTracks
|
||||||
|
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
import eu.kanade.domain.updates.repository.UpdatesRepository
|
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||||
|
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||||
|
import tachiyomi.data.source.SourceDataRepositoryImpl
|
||||||
|
import tachiyomi.data.track.TrackRepositoryImpl
|
||||||
|
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
||||||
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
|
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||||
|
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||||
|
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||||
|
import tachiyomi.domain.history.repository.HistoryRepository
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
import tachiyomi.domain.source.repository.SourceDataRepository
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addFactory
|
import uy.kohesive.injekt.api.addFactory
|
||||||
@ -93,7 +93,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetLibraryManga(get()) }
|
addFactory { GetLibraryManga(get()) }
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapter(get(), get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
@ -104,6 +104,7 @@ class DomainModule : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||||
addFactory { DeleteTrack(get()) }
|
addFactory { DeleteTrack(get()) }
|
||||||
|
addFactory { GetTracksPerManga(get()) }
|
||||||
addFactory { GetTracks(get()) }
|
addFactory { GetTracks(get()) }
|
||||||
addFactory { InsertTrack(get()) }
|
addFactory { InsertTrack(get()) }
|
||||||
|
|
||||||
@ -117,11 +118,10 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { DeleteAllHistory(get()) }
|
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
addFactory { UpsertHistory(get()) }
|
addFactory { UpsertHistory(get()) }
|
||||||
addFactory { RemoveHistoryById(get()) }
|
addFactory { RemoveHistory(get()) }
|
||||||
addFactory { RemoveHistoryByMangaId(get()) }
|
addFactory { GetTotalReadDuration(get()) }
|
||||||
|
|
||||||
addFactory { DeleteDownload(get(), get()) }
|
addFactory { DeleteDownload(get(), get()) }
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetExtensionLanguages(get(), get()) }
|
addFactory { GetExtensionLanguages(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
||||||
addFactory { GetUpdates(get(), get()) }
|
addFactory { GetUpdates(get()) }
|
||||||
|
|
||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.backup.service
|
package eu.kanade.domain.backup.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import eu.kanade.tachiyomi.core.provider.FolderProvider
|
import tachiyomi.core.provider.FolderProvider
|
||||||
|
|
||||||
class BackupPreferences(
|
class BackupPreferences(
|
||||||
private val folderProvider: FolderProvider,
|
private val folderProvider: FolderProvider,
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package eu.kanade.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@ -21,10 +18,7 @@ class BasePreferences(
|
|||||||
|
|
||||||
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
|
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
|
||||||
|
|
||||||
fun extensionInstaller() = preferenceStore.getEnum(
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
"extension_installer",
|
|
||||||
if (DeviceUtil.isMiui) PreferenceValues.ExtensionInstaller.LEGACY else PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.preference.getEnum
|
||||||
|
|
||||||
|
class ExtensionInstallerPreference(
|
||||||
|
private val context: Context,
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
) : Preference<ExtensionInstaller> {
|
||||||
|
|
||||||
|
private val basePref = preferenceStore.getEnum(key(), defaultValue())
|
||||||
|
|
||||||
|
override fun key() = "extension_installer"
|
||||||
|
|
||||||
|
val entries get() = ExtensionInstaller.values().run {
|
||||||
|
if (context.hasMiuiPackageInstaller) {
|
||||||
|
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||||
|
} else {
|
||||||
|
toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun defaultValue() = if (context.hasMiuiPackageInstaller) {
|
||||||
|
ExtensionInstaller.LEGACY
|
||||||
|
} else {
|
||||||
|
ExtensionInstaller.PACKAGEINSTALLER
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun check(value: ExtensionInstaller): ExtensionInstaller {
|
||||||
|
when (value) {
|
||||||
|
ExtensionInstaller.PACKAGEINSTALLER -> {
|
||||||
|
if (context.hasMiuiPackageInstaller) return ExtensionInstaller.LEGACY
|
||||||
|
}
|
||||||
|
ExtensionInstaller.SHIZUKU -> {
|
||||||
|
if (!context.isShizukuInstalled) return defaultValue()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(): ExtensionInstaller {
|
||||||
|
val value = basePref.get()
|
||||||
|
val checkedValue = check(value)
|
||||||
|
if (value != checkedValue) {
|
||||||
|
basePref.set(checkedValue)
|
||||||
|
}
|
||||||
|
return checkedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(value: ExtensionInstaller) {
|
||||||
|
basePref.set(check(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSet() = basePref.isSet()
|
||||||
|
|
||||||
|
override fun delete() = basePref.delete()
|
||||||
|
|
||||||
|
override fun changes() = basePref.changes()
|
||||||
|
|
||||||
|
override fun stateIn(scope: CoroutineScope) = basePref.stateIn(scope)
|
||||||
|
}
|
@ -1,12 +1,11 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.anyWithName
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
|
||||||
class CreateCategoryWithName(
|
class CreateCategoryWithName(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
@ -23,10 +22,6 @@ class CreateCategoryWithName(
|
|||||||
|
|
||||||
suspend fun await(name: String): Result = withNonCancellableContext {
|
suspend fun await(name: String): Result = withNonCancellableContext {
|
||||||
val categories = categoryRepository.getAll()
|
val categories = categoryRepository.getAll()
|
||||||
if (categories.anyWithName(name)) {
|
|
||||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
||||||
val newCategory = Category(
|
val newCategory = Category(
|
||||||
id = 0,
|
id = 0,
|
||||||
@ -46,7 +41,6 @@ class CreateCategoryWithName(
|
|||||||
|
|
||||||
sealed class Result {
|
sealed class Result {
|
||||||
object Success : Result()
|
object Success : Result()
|
||||||
object NameAlreadyExistsError : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
|
||||||
class DeleteCategory(
|
class DeleteCategory(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.model.anyWithName
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
|
||||||
class RenameCategory(
|
class RenameCategory(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
||||||
val categories = categoryRepository.getAll()
|
|
||||||
if (categories.anyWithName(name)) {
|
|
||||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
|
||||||
}
|
|
||||||
|
|
||||||
val update = CategoryUpdate(
|
val update = CategoryUpdate(
|
||||||
id = categoryId,
|
id = categoryId,
|
||||||
name = name,
|
name = name,
|
||||||
@ -36,7 +30,6 @@ class RenameCategory(
|
|||||||
|
|
||||||
sealed class Result {
|
sealed class Result {
|
||||||
object Success : Result()
|
object Success : Result()
|
||||||
object NameAlreadyExistsError : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,70 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
import kotlinx.coroutines.sync.withLock
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
class ReorderCategory(
|
class ReorderCategory(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, newPosition: Int) = withNonCancellableContext {
|
private val mutex = Mutex()
|
||||||
val categories = categoryRepository.getAll().filterNot(Category::isSystemCategory)
|
|
||||||
|
|
||||||
val currentIndex = categories.indexOfFirst { it.id == categoryId }
|
suspend fun moveUp(category: Category): Result =
|
||||||
if (currentIndex == newPosition) {
|
await(category, MoveTo.UP)
|
||||||
return@withNonCancellableContext Result.Unchanged
|
|
||||||
}
|
|
||||||
|
|
||||||
val reorderedCategories = categories.toMutableList()
|
suspend fun moveDown(category: Category): Result =
|
||||||
val reorderedCategory = reorderedCategories.removeAt(currentIndex)
|
await(category, MoveTo.DOWN)
|
||||||
reorderedCategories.add(newPosition, reorderedCategory)
|
|
||||||
|
|
||||||
val updates = reorderedCategories.mapIndexed { index, category ->
|
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
|
||||||
CategoryUpdate(
|
mutex.withLock {
|
||||||
id = category.id,
|
val categories = categoryRepository.getAll()
|
||||||
order = index.toLong(),
|
.filterNot(Category::isSystemCategory)
|
||||||
)
|
.toMutableList()
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
val currentIndex = categories.indexOfFirst { it.id == category.id }
|
||||||
categoryRepository.updatePartial(updates)
|
if (currentIndex == -1) {
|
||||||
Result.Success
|
return@withNonCancellableContext Result.Unchanged
|
||||||
} catch (e: Exception) {
|
}
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
Result.InternalError(e)
|
val newPosition = when (moveTo) {
|
||||||
|
MoveTo.UP -> currentIndex - 1
|
||||||
|
MoveTo.DOWN -> currentIndex + 1
|
||||||
|
}.toInt()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Collections.swap(categories, currentIndex, newPosition)
|
||||||
|
|
||||||
|
val updates = categories.mapIndexed { index, category ->
|
||||||
|
CategoryUpdate(
|
||||||
|
id = category.id,
|
||||||
|
order = index.toLong(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryRepository.updatePartial(updates)
|
||||||
|
Result.Success
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
Result.InternalError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(category: Category, newPosition: Long): Result =
|
|
||||||
await(category.id, newPosition.toInt())
|
|
||||||
|
|
||||||
sealed class Result {
|
sealed class Result {
|
||||||
object Success : Result()
|
object Success : Result()
|
||||||
object Unchanged : Result()
|
object Unchanged : Result()
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum class MoveTo {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.library.model.plus
|
||||||
|
|
||||||
class ResetCategoryFlags(
|
class ResetCategoryFlags(
|
||||||
private val preferences: LibraryPreferences,
|
private val preferences: LibraryPreferences,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
import tachiyomi.domain.library.model.plus
|
||||||
|
|
||||||
class SetDisplayModeForCategory(
|
class SetDisplayModeForCategory(
|
||||||
private val preferences: LibraryPreferences,
|
private val preferences: LibraryPreferences,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class SetMangaCategories(
|
class SetMangaCategories(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
|
||||||
import eu.kanade.domain.library.model.LibrarySort
|
|
||||||
import eu.kanade.domain.library.model.plus
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.library.model.LibrarySort
|
||||||
|
import tachiyomi.domain.library.model.plus
|
||||||
|
|
||||||
class SetSortModeForCategory(
|
class SetSortModeForCategory(
|
||||||
private val preferences: LibraryPreferences,
|
private val preferences: LibraryPreferences,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
package eu.kanade.domain.category.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.category.model.CategoryUpdate
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import eu.kanade.domain.category.repository.CategoryRepository
|
import tachiyomi.domain.category.model.CategoryUpdate
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
|
||||||
class UpdateCategory(
|
class UpdateCategory(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
class GetChapter(
|
class GetChapter(
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
class GetChapterByMangaId(
|
class GetChapterByMangaId(
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
|
@ -3,8 +3,8 @@ package eu.kanade.domain.chapter.interactor
|
|||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
class SetMangaDefaultChapterFlags(
|
class SetMangaDefaultChapterFlags(
|
||||||
private val libraryPreferences: LibraryPreferences,
|
private val libraryPreferences: LibraryPreferences,
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
import eu.kanade.domain.download.service.DownloadPreferences
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class SetReadStatus(
|
class SetReadStatus(
|
||||||
private val downloadPreferences: DownloadPreferences,
|
private val downloadPreferences: DownloadPreferences,
|
||||||
@ -27,7 +27,12 @@ class SetReadStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||||
val chaptersToUpdate = chapters.filterNot { it.read == read }
|
val chaptersToUpdate = chapters.filter {
|
||||||
|
when (read) {
|
||||||
|
true -> !it.read
|
||||||
|
false -> it.read || it.lastPageRead > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
if (chaptersToUpdate.isEmpty()) {
|
if (chaptersToUpdate.isEmpty()) {
|
||||||
return@withNonCancellableContext Result.NoChapters
|
return@withNonCancellableContext Result.NoChapters
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.data.chapter.CleanupChapterName
|
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||||
import eu.kanade.data.chapter.NoChaptersException
|
import eu.kanade.domain.chapter.model.toSChapter
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.model.toDbChapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
@ -15,6 +11,13 @@ import eu.kanade.tachiyomi.source.isLocal
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||||
|
import tachiyomi.data.chapter.ChapterSanitizer
|
||||||
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||||
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
@ -53,7 +56,7 @@ class SyncChaptersWithSource(
|
|||||||
.mapIndexed { i, sChapter ->
|
.mapIndexed { i, sChapter ->
|
||||||
Chapter.create()
|
Chapter.create()
|
||||||
.copyFromSChapter(sChapter)
|
.copyFromSChapter(sChapter)
|
||||||
.copy(name = CleanupChapterName.await(sChapter.name, manga.title))
|
.copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) })
|
||||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +114,7 @@ class SyncChaptersWithSource(
|
|||||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter.toDbChapter(), chapter.toDbChapter())
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
}
|
}
|
||||||
var toChangeChapter = dbChapter.copy(
|
var toChangeChapter = dbChapter.copy(
|
||||||
name = chapter.name,
|
name = chapter.name,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
class UpdateChapter(
|
class UpdateChapter(
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
|
@ -2,64 +2,30 @@ package eu.kanade.domain.chapter.model
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||||
|
|
||||||
data class Chapter(
|
// TODO: Remove when all deps are migrated
|
||||||
val id: Long,
|
fun Chapter.toSChapter(): SChapter {
|
||||||
val mangaId: Long,
|
return SChapter.create().also {
|
||||||
val read: Boolean,
|
it.url = url
|
||||||
val bookmark: Boolean,
|
it.name = name
|
||||||
val lastPageRead: Long,
|
it.date_upload = dateUpload
|
||||||
val dateFetch: Long,
|
it.chapter_number = chapterNumber
|
||||||
val sourceOrder: Long,
|
it.scanlator = scanlator
|
||||||
val url: String,
|
|
||||||
val name: String,
|
|
||||||
val dateUpload: Long,
|
|
||||||
val chapterNumber: Float,
|
|
||||||
val scanlator: String?,
|
|
||||||
) {
|
|
||||||
val isRecognizedNumber: Boolean
|
|
||||||
get() = chapterNumber >= 0f
|
|
||||||
|
|
||||||
fun toSChapter(): SChapter {
|
|
||||||
return SChapter.create().also {
|
|
||||||
it.url = url
|
|
||||||
it.name = name
|
|
||||||
it.date_upload = dateUpload
|
|
||||||
it.chapter_number = chapterNumber
|
|
||||||
it.scanlator = scanlator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyFromSChapter(sChapter: SChapter): Chapter {
|
|
||||||
return this.copy(
|
|
||||||
name = sChapter.name,
|
|
||||||
url = sChapter.url,
|
|
||||||
dateUpload = sChapter.date_upload,
|
|
||||||
chapterNumber = sChapter.chapter_number,
|
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun create() = Chapter(
|
|
||||||
id = -1,
|
|
||||||
mangaId = -1,
|
|
||||||
read = false,
|
|
||||||
bookmark = false,
|
|
||||||
lastPageRead = 0,
|
|
||||||
dateFetch = 0,
|
|
||||||
sourceOrder = 0,
|
|
||||||
url = "",
|
|
||||||
name = "",
|
|
||||||
dateUpload = -1,
|
|
||||||
chapterNumber = -1f,
|
|
||||||
scanlator = null,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove when all deps are migrated
|
fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||||
|
return this.copy(
|
||||||
|
name = sChapter.name,
|
||||||
|
url = sChapter.url,
|
||||||
|
dateUpload = sChapter.date_upload,
|
||||||
|
chapterNumber = sChapter.chapter_number,
|
||||||
|
scanlator = sChapter.scanlator?.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.manga_id = mangaId
|
it.manga_id = mangaId
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.domain.chapter.model
|
||||||
|
|
||||||
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
|
import eu.kanade.domain.manga.model.isLocal
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.TriStateFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
|
*/
|
||||||
|
fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
|
||||||
|
val isLocalManga = manga.isLocal()
|
||||||
|
val unreadFilter = manga.unreadFilter
|
||||||
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
|
|
||||||
|
return filter { chapter ->
|
||||||
|
when (unreadFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> !chapter.read
|
||||||
|
TriStateFilter.ENABLED_NOT -> chapter.read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { chapter ->
|
||||||
|
when (bookmarkedFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
||||||
|
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { chapter ->
|
||||||
|
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
||||||
|
val downloadState = when {
|
||||||
|
downloaded -> Download.State.DOWNLOADED
|
||||||
|
else -> Download.State.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
when (downloadedFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> downloadState == Download.State.DOWNLOADED || isLocalManga
|
||||||
|
TriStateFilter.ENABLED_NOT -> downloadState != Download.State.DOWNLOADED && !isLocalManga
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedWith(getChapterSort(manga))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
|
*/
|
||||||
|
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
||||||
|
val isLocalManga = manga.isLocal()
|
||||||
|
val unreadFilter = manga.unreadFilter
|
||||||
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
|
return asSequence()
|
||||||
|
.filter { (chapter) ->
|
||||||
|
when (unreadFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> !chapter.read
|
||||||
|
TriStateFilter.ENABLED_NOT -> chapter.read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { (chapter) ->
|
||||||
|
when (bookmarkedFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
||||||
|
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter {
|
||||||
|
when (downloadedFilter) {
|
||||||
|
TriStateFilter.DISABLED -> true
|
||||||
|
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
|
||||||
|
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
package eu.kanade.domain.download.interactor
|
package eu.kanade.domain.download.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.model.toDbChapter
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
class DeleteDownload(
|
class DeleteDownload(
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
@ -14,7 +13,7 @@ class DeleteDownload(
|
|||||||
|
|
||||||
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
||||||
sourceManager.get(manga.source)?.let { source ->
|
sourceManager.get(manga.source)?.let { source ->
|
||||||
downloadManager.deleteChapters(chapters.map { it.toDbChapter() }, manga, source)
|
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.download.service
|
package eu.kanade.domain.download.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import eu.kanade.tachiyomi.core.provider.FolderProvider
|
import tachiyomi.core.provider.FolderProvider
|
||||||
|
|
||||||
class DownloadPreferences(
|
class DownloadPreferences(
|
||||||
private val folderProvider: FolderProvider,
|
private val folderProvider: FolderProvider,
|
||||||
|
@ -25,10 +25,7 @@ class GetExtensionLanguages(
|
|||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareBy(
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
{ it !in enabledLanguage },
|
|
||||||
{ LocaleHelper.getDisplayName(it) },
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.domain.extension.interactor
|
|||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@ -30,3 +29,9 @@ class GetExtensionSources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ExtensionSourceItem(
|
||||||
|
val source: Source,
|
||||||
|
val enabled: Boolean,
|
||||||
|
val labelAsName: Boolean,
|
||||||
|
)
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
|
|
||||||
class DeleteAllHistory(
|
|
||||||
private val repository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): Boolean {
|
|
||||||
return repository.deleteAllHistory()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
|
||||||
|
|
||||||
class GetNextChapter(
|
|
||||||
private val getChapter: GetChapter,
|
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
|
||||||
private val getManga: GetManga,
|
|
||||||
private val historyRepository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): Chapter? {
|
|
||||||
val history = historyRepository.getLastHistory() ?: return null
|
|
||||||
return await(history.mangaId, history.chapterId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
|
||||||
val chapter = getChapter.await(chapterId)!!
|
|
||||||
val manga = getManga.await(mangaId)!!
|
|
||||||
|
|
||||||
if (!chapter.read) return chapter
|
|
||||||
|
|
||||||
val chapters = getChapterByMangaId.await(mangaId)
|
|
||||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
|
||||||
|
|
||||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
|
||||||
return when (manga.sorting) {
|
|
||||||
Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
|
|
||||||
Manga.CHAPTER_SORTING_NUMBER -> {
|
|
||||||
val chapterNumber = chapter.chapterNumber
|
|
||||||
|
|
||||||
((currChapterIndex + 1) until chapters.size)
|
|
||||||
.map { chapters[it] }
|
|
||||||
.firstOrNull {
|
|
||||||
it.chapterNumber > chapterNumber && it.chapterNumber <= chapterNumber + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
|
|
||||||
chapters.drop(currChapterIndex + 1)
|
|
||||||
.firstOrNull { it.dateUpload >= chapter.dateUpload }
|
|
||||||
}
|
|
||||||
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
package eu.kanade.domain.history.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.history.repository.HistoryRepository
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class GetNextChapters(
|
||||||
|
private val getChapterByMangaId: GetChapterByMangaId,
|
||||||
|
private val getManga: GetManga,
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(onlyUnread: Boolean = true): List<Chapter> {
|
||||||
|
val history = historyRepository.getLastHistory() ?: return emptyList()
|
||||||
|
return await(history.mangaId, history.chapterId, onlyUnread)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||||
|
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||||
|
val chapters = getChapterByMangaId.await(mangaId)
|
||||||
|
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||||
|
|
||||||
|
return if (onlyUnread) {
|
||||||
|
chapters.filterNot { it.read }
|
||||||
|
} else {
|
||||||
|
chapters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, fromChapterId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||||
|
val chapters = await(mangaId, onlyUnread)
|
||||||
|
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
|
||||||
|
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)
|
||||||
|
|
||||||
|
if (onlyUnread) {
|
||||||
|
return nextChapters
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "next chapter" is either:
|
||||||
|
// - The current chapter if it isn't completely read
|
||||||
|
// - The chapters after the current chapter if the current one is completely read
|
||||||
|
val fromChapter = chapters.getOrNull(currChapterIndex)
|
||||||
|
return if (fromChapter != null && !fromChapter.read) {
|
||||||
|
nextChapters
|
||||||
|
} else {
|
||||||
|
nextChapters.drop(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
|
|
||||||
class RemoveHistoryById(
|
|
||||||
private val repository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(history: HistoryWithRelations) {
|
|
||||||
repository.resetHistory(history.id)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
|
|
||||||
class RemoveHistoryByMangaId(
|
|
||||||
private val repository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long) {
|
|
||||||
repository.resetHistoryByMangaId(mangaId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,14 @@
|
|||||||
package eu.kanade.domain.library.service
|
package eu.kanade.domain.library.service
|
||||||
|
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
|
||||||
import eu.kanade.domain.library.model.LibrarySort
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
|
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
import tachiyomi.domain.library.model.LibrarySort
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
class LibraryPreferences(
|
class LibraryPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@ -22,7 +22,7 @@ class LibraryPreferences(
|
|||||||
|
|
||||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
||||||
|
|
||||||
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 24)
|
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
||||||
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
||||||
|
|
||||||
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
||||||
@ -32,6 +32,8 @@ class LibraryPreferences(
|
|||||||
|
|
||||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
||||||
|
|
||||||
|
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
|
||||||
|
|
||||||
// region Filter
|
// region Filter
|
||||||
|
|
||||||
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||||
@ -54,12 +56,10 @@ class LibraryPreferences(
|
|||||||
|
|
||||||
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
|
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
|
||||||
|
|
||||||
fun unreadBadge() = preferenceStore.getBoolean("display_unread_badge", true)
|
|
||||||
|
|
||||||
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
||||||
|
|
||||||
fun showUpdatesNavBadge() = preferenceStore.getBoolean("library_update_show_tab_badge", false)
|
fun newShowUpdatesCount() = preferenceStore.getBoolean("library_show_updates_count", true)
|
||||||
fun unreadUpdatesCount() = preferenceStore.getInt("library_unread_updates_count", 0)
|
fun newUpdatesCount() = preferenceStore.getInt("library_unseen_updates_count", 0)
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class GetDuplicateLibraryManga(
|
class GetDuplicateLibraryManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(title: String, sourceId: Long): Manga? {
|
suspend fun await(title: String): Manga? {
|
||||||
return mangaRepository.getDuplicateLibraryManga(title.lowercase(), sourceId)
|
return mangaRepository.getDuplicateLibraryManga(title.lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class GetFavorites(
|
class GetFavorites(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class GetLibraryManga(
|
class GetLibraryManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class GetManga(
|
class GetManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class GetMangaWithChapters(
|
class GetMangaWithChapters(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class NetworkToLocalManga(
|
class NetworkToLocalManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(manga: Manga, sourceId: Long): Manga {
|
suspend fun await(manga: Manga): Manga {
|
||||||
val localManga = getManga(manga.url, sourceId)
|
val localManga = getManga(manga.url, manga.source)
|
||||||
return when {
|
return when {
|
||||||
localManga == null -> {
|
localManga == null -> {
|
||||||
val id = insertManga(manga.copy(source = sourceId))
|
val id = insertManga(manga)
|
||||||
manga.copy(id = id!!)
|
manga.copy(id = id!!)
|
||||||
}
|
}
|
||||||
!localManga.favorite -> {
|
!localManga.favorite -> {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class ResetViewerFlags(
|
class ResetViewerFlags(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class SetMangaChapterFlags(
|
class SetMangaChapterFlags(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
@ -1,28 +1,30 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.MangaUpdate
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
class SetMangaViewerFlags(
|
class SetMangaViewerFlags(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
||||||
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = flag.setFlag(flag, ReadingModeType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
||||||
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = flag.setFlag(flag, OrientationType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.model.MangaUpdate
|
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
import eu.kanade.domain.manga.model.isLocal
|
||||||
import eu.kanade.domain.manga.model.toDbManga
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -46,11 +45,11 @@ class UpdateManga(
|
|||||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||||
localManga.isLocal() -> Date().time
|
localManga.isLocal() -> Date().time
|
||||||
localManga.hasCustomCover(coverCache) -> {
|
localManga.hasCustomCover(coverCache) -> {
|
||||||
coverCache.deleteFromCache(localManga.toDbManga(), false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
coverCache.deleteFromCache(localManga.toDbManga(), false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
Date().time
|
Date().time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,60 +1,176 @@
|
|||||||
package eu.kanade.domain.manga.model
|
package eu.kanade.domain.manga.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import nl.adaptivity.xmlutil.serialization.XmlElement
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlValue
|
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
|
const val COMIC_INFO_FILE = "ComicInfo.xml"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||||
|
*/
|
||||||
|
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo(
|
||||||
|
title = ComicInfo.Title(chapter.name),
|
||||||
|
series = ComicInfo.Series(manga.title),
|
||||||
|
web = ComicInfo.Web(chapterUrl),
|
||||||
|
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||||
|
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||||
|
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
||||||
|
translator = chapter.scanlator?.let { ComicInfo.Translator(it) },
|
||||||
|
genre = manga.genre?.let { ComicInfo.Genre(it.joinToString()) },
|
||||||
|
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||||
|
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||||
|
),
|
||||||
|
inker = null,
|
||||||
|
colorist = null,
|
||||||
|
letterer = null,
|
||||||
|
coverArtist = null,
|
||||||
|
tags = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
||||||
|
comicInfo.series?.let { title = it.value }
|
||||||
|
comicInfo.writer?.let { author = it.value }
|
||||||
|
comicInfo.summary?.let { description = it.value }
|
||||||
|
|
||||||
|
listOfNotNull(
|
||||||
|
comicInfo.genre?.value,
|
||||||
|
comicInfo.tags?.value,
|
||||||
|
)
|
||||||
|
.flatMap { it.split(", ") }
|
||||||
|
.distinct()
|
||||||
|
.joinToString(", ") { it.trim() }
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { genre = it }
|
||||||
|
|
||||||
|
listOfNotNull(
|
||||||
|
comicInfo.penciller?.value,
|
||||||
|
comicInfo.inker?.value,
|
||||||
|
comicInfo.colorist?.value,
|
||||||
|
comicInfo.letterer?.value,
|
||||||
|
comicInfo.coverArtist?.value,
|
||||||
|
)
|
||||||
|
.flatMap { it.split(", ") }
|
||||||
|
.distinct()
|
||||||
|
.joinToString(", ") { it.trim() }
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { artist = it }
|
||||||
|
|
||||||
|
status = ComicInfoPublishingStatus.toSMangaValue(comicInfo.publishingStatus?.value)
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("ComicInfo", "", "")
|
@XmlSerialName("ComicInfo", "", "")
|
||||||
data class ComicInfo(
|
data class ComicInfo(
|
||||||
val series: ComicInfoSeries?,
|
val title: Title?,
|
||||||
val summary: ComicInfoSummary?,
|
val series: Series?,
|
||||||
val writer: ComicInfoWriter?,
|
val summary: Summary?,
|
||||||
val penciller: ComicInfoPenciller?,
|
val writer: Writer?,
|
||||||
val inker: ComicInfoInker?,
|
val penciller: Penciller?,
|
||||||
val colorist: ComicInfoColorist?,
|
val inker: Inker?,
|
||||||
val letterer: ComicInfoLetterer?,
|
val colorist: Colorist?,
|
||||||
val coverArtist: ComicInfoCoverArtist?,
|
val letterer: Letterer?,
|
||||||
val genre: ComicInfoGenre?,
|
val coverArtist: CoverArtist?,
|
||||||
val tags: ComicInfoTags?,
|
val translator: Translator?,
|
||||||
)
|
val genre: Genre?,
|
||||||
|
val tags: Tags?,
|
||||||
|
val web: Web?,
|
||||||
|
val publishingStatus: PublishingStatusTachiyomi?,
|
||||||
|
) {
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
@XmlElement(false)
|
||||||
|
@XmlSerialName("xmlns:xsd", "", "")
|
||||||
|
val xmlSchema: String = "http://www.w3.org/2001/XMLSchema"
|
||||||
|
|
||||||
@Serializable
|
@Suppress("UNUSED")
|
||||||
@XmlSerialName("Series", "", "")
|
@XmlElement(false)
|
||||||
data class ComicInfoSeries(@XmlValue(true) val value: String = "")
|
@XmlSerialName("xmlns:xsi", "", "")
|
||||||
|
val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Summary", "", "")
|
@XmlSerialName("Title", "", "")
|
||||||
data class ComicInfoSummary(@XmlValue(true) val value: String = "")
|
data class Title(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Writer", "", "")
|
@XmlSerialName("Series", "", "")
|
||||||
data class ComicInfoWriter(@XmlValue(true) val value: String = "")
|
data class Series(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Penciller", "", "")
|
@XmlSerialName("Summary", "", "")
|
||||||
data class ComicInfoPenciller(@XmlValue(true) val value: String = "")
|
data class Summary(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Inker", "", "")
|
@XmlSerialName("Writer", "", "")
|
||||||
data class ComicInfoInker(@XmlValue(true) val value: String = "")
|
data class Writer(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Colorist", "", "")
|
@XmlSerialName("Penciller", "", "")
|
||||||
data class ComicInfoColorist(@XmlValue(true) val value: String = "")
|
data class Penciller(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Letterer", "", "")
|
@XmlSerialName("Inker", "", "")
|
||||||
data class ComicInfoLetterer(@XmlValue(true) val value: String = "")
|
data class Inker(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("CoverArtist", "", "")
|
@XmlSerialName("Colorist", "", "")
|
||||||
data class ComicInfoCoverArtist(@XmlValue(true) val value: String = "")
|
data class Colorist(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Genre", "", "")
|
@XmlSerialName("Letterer", "", "")
|
||||||
data class ComicInfoGenre(@XmlValue(true) val value: String = "")
|
data class Letterer(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@XmlSerialName("Tags", "", "")
|
@XmlSerialName("CoverArtist", "", "")
|
||||||
data class ComicInfoTags(@XmlValue(true) val value: String = "")
|
data class CoverArtist(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("Translator", "", "")
|
||||||
|
data class Translator(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("Genre", "", "")
|
||||||
|
data class Genre(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("Tags", "", "")
|
||||||
|
data class Tags(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("Web", "", "")
|
||||||
|
data class Web(@XmlValue(true) val value: String = "")
|
||||||
|
|
||||||
|
// The spec doesn't have a good field for this
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty")
|
||||||
|
data class PublishingStatusTachiyomi(@XmlValue(true) val value: String = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class ComicInfoPublishingStatus(
|
||||||
|
val comicInfoValue: String,
|
||||||
|
val sMangaModelValue: Int,
|
||||||
|
) {
|
||||||
|
ONGOING("Ongoing", SManga.ONGOING),
|
||||||
|
COMPLETED("Completed", SManga.COMPLETED),
|
||||||
|
LICENSED("Licensed", SManga.LICENSED),
|
||||||
|
PUBLISHING_FINISHED("Publishing finished", SManga.PUBLISHING_FINISHED),
|
||||||
|
CANCELLED("Cancelled", SManga.CANCELLED),
|
||||||
|
ON_HIATUS("On hiatus", SManga.ON_HIATUS),
|
||||||
|
UNKNOWN("Unknown", SManga.UNKNOWN),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun toComicInfoValue(value: Long): String {
|
||||||
|
return values().firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
|
||||||
|
?: UNKNOWN.comicInfoValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toSMangaValue(value: String?): Int {
|
||||||
|
return values().firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
|
||||||
|
?: UNKNOWN.sMangaModelValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,238 +1,76 @@
|
|||||||
package eu.kanade.domain.manga.model
|
package eu.kanade.domain.manga.model
|
||||||
|
|
||||||
import eu.kanade.data.listOfStringsAdapter
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.TriStateFilter
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.Serializable
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
|
||||||
|
|
||||||
data class Manga(
|
// TODO: move these into the domain model
|
||||||
val id: Long,
|
val Manga.readingModeType: Long
|
||||||
val source: Long,
|
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
||||||
val favorite: Boolean,
|
|
||||||
val lastUpdate: Long,
|
|
||||||
val dateAdded: Long,
|
|
||||||
val viewerFlags: Long,
|
|
||||||
val chapterFlags: Long,
|
|
||||||
val coverLastModified: Long,
|
|
||||||
val url: String,
|
|
||||||
val title: String,
|
|
||||||
val artist: String?,
|
|
||||||
val author: String?,
|
|
||||||
val description: String?,
|
|
||||||
val genre: List<String>?,
|
|
||||||
val status: Long,
|
|
||||||
val thumbnailUrl: String?,
|
|
||||||
val updateStrategy: UpdateStrategy,
|
|
||||||
val initialized: Boolean,
|
|
||||||
) : Serializable {
|
|
||||||
|
|
||||||
val sorting: Long
|
val Manga.orientationType: Long
|
||||||
get() = chapterFlags and CHAPTER_SORTING_MASK
|
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||||
|
|
||||||
val displayMode: Long
|
val Manga.downloadedFilter: TriStateFilter
|
||||||
get() = chapterFlags and CHAPTER_DISPLAY_MASK
|
get() {
|
||||||
|
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||||
val unreadFilterRaw: Long
|
return when (downloadedFilterRaw) {
|
||||||
get() = chapterFlags and CHAPTER_UNREAD_MASK
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||||
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||||
val downloadedFilterRaw: Long
|
|
||||||
get() = chapterFlags and CHAPTER_DOWNLOADED_MASK
|
|
||||||
|
|
||||||
val bookmarkedFilterRaw: Long
|
|
||||||
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
|
|
||||||
|
|
||||||
val unreadFilter: TriStateFilter
|
|
||||||
get() = when (unreadFilterRaw) {
|
|
||||||
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS
|
|
||||||
CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT
|
|
||||||
else -> TriStateFilter.DISABLED
|
else -> TriStateFilter.DISABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadedFilter: TriStateFilter
|
|
||||||
get() {
|
|
||||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
|
||||||
return when (downloadedFilterRaw) {
|
|
||||||
CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
|
||||||
CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
|
||||||
else -> TriStateFilter.DISABLED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bookmarkedFilter: TriStateFilter
|
|
||||||
get() = when (bookmarkedFilterRaw) {
|
|
||||||
CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
|
||||||
CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
|
||||||
else -> TriStateFilter.DISABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
fun chaptersFiltered(): Boolean {
|
|
||||||
return unreadFilter != TriStateFilter.DISABLED ||
|
|
||||||
downloadedFilter != TriStateFilter.DISABLED ||
|
|
||||||
bookmarkedFilter != TriStateFilter.DISABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
fun forceDownloaded(): Boolean {
|
|
||||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sortDescending(): Boolean {
|
|
||||||
return chapterFlags and CHAPTER_SORT_DIR_MASK == CHAPTER_SORT_DESC
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toSManga(): SManga = SManga.create().also {
|
|
||||||
it.url = url
|
|
||||||
it.title = title
|
|
||||||
it.artist = artist
|
|
||||||
it.author = author
|
|
||||||
it.description = description
|
|
||||||
it.genre = genre.orEmpty().joinToString()
|
|
||||||
it.status = status.toInt()
|
|
||||||
it.thumbnail_url = thumbnailUrl
|
|
||||||
it.initialized = initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyFrom(other: SManga): Manga {
|
|
||||||
val author = other.author ?: author
|
|
||||||
val artist = other.artist ?: artist
|
|
||||||
val description = other.description ?: description
|
|
||||||
val genres = if (other.genre != null) {
|
|
||||||
other.getGenres()
|
|
||||||
} else {
|
|
||||||
genre
|
|
||||||
}
|
|
||||||
val thumbnailUrl = other.thumbnail_url ?: thumbnailUrl
|
|
||||||
return this.copy(
|
|
||||||
author = author,
|
|
||||||
artist = artist,
|
|
||||||
description = description,
|
|
||||||
genre = genres,
|
|
||||||
thumbnailUrl = thumbnailUrl,
|
|
||||||
status = other.status.toLong(),
|
|
||||||
updateStrategy = other.update_strategy,
|
|
||||||
initialized = other.initialized && initialized,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// Generic filter that does not filter anything
|
|
||||||
const val SHOW_ALL = 0x00000000L
|
|
||||||
|
|
||||||
const val CHAPTER_SORT_DESC = 0x00000000L
|
|
||||||
const val CHAPTER_SORT_ASC = 0x00000001L
|
|
||||||
const val CHAPTER_SORT_DIR_MASK = 0x00000001L
|
|
||||||
|
|
||||||
const val CHAPTER_SHOW_UNREAD = 0x00000002L
|
|
||||||
const val CHAPTER_SHOW_READ = 0x00000004L
|
|
||||||
const val CHAPTER_UNREAD_MASK = 0x00000006L
|
|
||||||
|
|
||||||
const val CHAPTER_SHOW_DOWNLOADED = 0x00000008L
|
|
||||||
const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010L
|
|
||||||
const val CHAPTER_DOWNLOADED_MASK = 0x00000018L
|
|
||||||
|
|
||||||
const val CHAPTER_SHOW_BOOKMARKED = 0x00000020L
|
|
||||||
const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040L
|
|
||||||
const val CHAPTER_BOOKMARKED_MASK = 0x00000060L
|
|
||||||
|
|
||||||
const val CHAPTER_SORTING_SOURCE = 0x00000000L
|
|
||||||
const val CHAPTER_SORTING_NUMBER = 0x00000100L
|
|
||||||
const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L
|
|
||||||
const val CHAPTER_SORTING_MASK = 0x00000300L
|
|
||||||
|
|
||||||
const val CHAPTER_DISPLAY_NAME = 0x00000000L
|
|
||||||
const val CHAPTER_DISPLAY_NUMBER = 0x00100000L
|
|
||||||
const val CHAPTER_DISPLAY_MASK = 0x00100000L
|
|
||||||
|
|
||||||
fun create() = Manga(
|
|
||||||
id = -1L,
|
|
||||||
url = "",
|
|
||||||
title = "",
|
|
||||||
source = -1L,
|
|
||||||
favorite = false,
|
|
||||||
lastUpdate = 0L,
|
|
||||||
dateAdded = 0L,
|
|
||||||
viewerFlags = 0L,
|
|
||||||
chapterFlags = 0L,
|
|
||||||
coverLastModified = 0L,
|
|
||||||
artist = null,
|
|
||||||
author = null,
|
|
||||||
description = null,
|
|
||||||
genre = null,
|
|
||||||
status = 0L,
|
|
||||||
thumbnailUrl = null,
|
|
||||||
updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
|
||||||
initialized = false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
fun Manga.chaptersFiltered(): Boolean {
|
||||||
|
return unreadFilter != TriStateFilter.DISABLED ||
|
||||||
|
downloadedFilter != TriStateFilter.DISABLED ||
|
||||||
|
bookmarkedFilter != TriStateFilter.DISABLED
|
||||||
|
}
|
||||||
|
fun Manga.forceDownloaded(): Boolean {
|
||||||
|
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TriStateFilter {
|
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||||
DISABLED, // Disable filter
|
|
||||||
ENABLED_IS, // Enabled with "is" filter
|
|
||||||
ENABLED_NOT, // Enabled with "not" filter
|
|
||||||
}
|
|
||||||
|
|
||||||
fun TriStateFilter.toTriStateGroupState(): ExtendedNavigationView.Item.TriStateGroup.State {
|
|
||||||
return when (this) {
|
|
||||||
TriStateFilter.DISABLED -> ExtendedNavigationView.Item.TriStateGroup.State.IGNORE
|
|
||||||
TriStateFilter.ENABLED_IS -> ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE
|
|
||||||
TriStateFilter.ENABLED_NOT -> ExtendedNavigationView.Item.TriStateGroup.State.EXCLUDE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove when all deps are migrated
|
|
||||||
fun Manga.toDbManga(): DbManga = MangaImpl().also {
|
|
||||||
it.id = id
|
|
||||||
it.source = source
|
|
||||||
it.favorite = favorite
|
|
||||||
it.last_update = lastUpdate
|
|
||||||
it.date_added = dateAdded
|
|
||||||
it.viewer_flags = viewerFlags.toInt()
|
|
||||||
it.chapter_flags = chapterFlags.toInt()
|
|
||||||
it.cover_last_modified = coverLastModified
|
|
||||||
it.url = url
|
it.url = url
|
||||||
it.title = title
|
it.title = title
|
||||||
it.artist = artist
|
it.artist = artist
|
||||||
it.author = author
|
it.author = author
|
||||||
it.description = description
|
it.description = description
|
||||||
it.genre = genre?.let(listOfStringsAdapter::encode)
|
it.genre = genre.orEmpty().joinToString()
|
||||||
it.status = status.toInt()
|
it.status = status.toInt()
|
||||||
it.thumbnail_url = thumbnailUrl
|
it.thumbnail_url = thumbnailUrl
|
||||||
it.update_strategy = updateStrategy
|
|
||||||
it.initialized = initialized
|
it.initialized = initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Manga.toMangaUpdate(): MangaUpdate {
|
fun Manga.copyFrom(other: SManga): Manga {
|
||||||
return MangaUpdate(
|
val author = other.author ?: author
|
||||||
id = id,
|
val artist = other.artist ?: artist
|
||||||
source = source,
|
val description = other.description ?: description
|
||||||
favorite = favorite,
|
val genres = if (other.genre != null) {
|
||||||
lastUpdate = lastUpdate,
|
other.getGenres()
|
||||||
dateAdded = dateAdded,
|
} else {
|
||||||
viewerFlags = viewerFlags,
|
genre
|
||||||
chapterFlags = chapterFlags,
|
}
|
||||||
coverLastModified = coverLastModified,
|
val thumbnailUrl = other.thumbnail_url ?: thumbnailUrl
|
||||||
url = url,
|
return this.copy(
|
||||||
title = title,
|
|
||||||
artist = artist,
|
|
||||||
author = author,
|
author = author,
|
||||||
|
artist = artist,
|
||||||
description = description,
|
description = description,
|
||||||
genre = genre,
|
genre = genres,
|
||||||
status = status,
|
|
||||||
thumbnailUrl = thumbnailUrl,
|
thumbnailUrl = thumbnailUrl,
|
||||||
updateStrategy = updateStrategy,
|
status = other.status.toLong(),
|
||||||
initialized = initialized,
|
updateStrategy = other.update_strategy,
|
||||||
|
initialized = other.initialized && initialized,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SManga.toDomainManga(): Manga {
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
return Manga.create().copy(
|
return Manga.create().copy(
|
||||||
url = url,
|
url = url,
|
||||||
title = title,
|
title = title,
|
||||||
@ -244,6 +82,7 @@ fun SManga.toDomainManga(): Manga {
|
|||||||
thumbnailUrl = thumbnail_url,
|
thumbnailUrl = thumbnail_url,
|
||||||
updateStrategy = update_strategy,
|
updateStrategy = update_strategy,
|
||||||
initialized = initialized,
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.model
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the required data for MangaCoverFetcher
|
|
||||||
*/
|
|
||||||
data class MangaCover(
|
|
||||||
val mangaId: Long,
|
|
||||||
val sourceId: Long,
|
|
||||||
val isMangaFavorite: Boolean,
|
|
||||||
val url: String?,
|
|
||||||
val lastModified: Long,
|
|
||||||
)
|
|
@ -1,14 +1,14 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Pin
|
|
||||||
import eu.kanade.domain.source.model.Pins
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import tachiyomi.domain.source.model.Pin
|
||||||
|
import tachiyomi.domain.source.model.Pins
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
class GetEnabledSources(
|
class GetEnabledSources(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
@ -23,7 +23,6 @@ class GetEnabledSources(
|
|||||||
preferences.lastUsedSource().changes(),
|
preferences.lastUsedSource().changes(),
|
||||||
repository.getSources(),
|
repository.getSources(),
|
||||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||||
val duplicatePins = preferences.duplicatePinnedSources().get()
|
|
||||||
sources
|
sources
|
||||||
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
||||||
.filterNot { it.id.toString() in disabledSources }
|
.filterNot { it.id.toString() in disabledSources }
|
||||||
@ -35,10 +34,6 @@ class GetEnabledSources(
|
|||||||
if (source.id == lastUsedSource) {
|
if (source.id == lastUsedSource) {
|
||||||
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
||||||
}
|
}
|
||||||
if (duplicatePins && Pin.Pinned in source.pin) {
|
|
||||||
toFlatten[0] = toFlatten[0].copy(pin = source.pin + Pin.Forced)
|
|
||||||
toFlatten.add(source.copy(pin = source.pin - Pin.Actual))
|
|
||||||
}
|
|
||||||
toFlatten
|
toFlatten
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
class GetLanguagesWithSources(
|
class GetLanguagesWithSources(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
@ -25,10 +25,7 @@ class GetLanguagesWithSources(
|
|||||||
|
|
||||||
sortedSources.groupBy { it.lang }
|
sortedSources.groupBy { it.lang }
|
||||||
.toSortedMap(
|
.toSortedMap(
|
||||||
compareBy(
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
{ it !in enabledLanguage },
|
|
||||||
{ LocaleHelper.getDisplayName(it) },
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.SourceWithCount
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
|
|
||||||
class GetSourcesWithNonLibraryManga(
|
class GetSourcesWithNonLibraryManga(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
|
|
||||||
class ToggleLanguage(
|
class ToggleLanguage(
|
||||||
val preferences: SourcePreferences,
|
val preferences: SourcePreferences,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
class ToggleSource(
|
class ToggleSource(
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
@ -18,6 +18,13 @@ class ToggleSource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||||
|
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||||
|
preferences.disabledSources().getAndSet { disabled ->
|
||||||
|
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isEnabled(sourceId: Long): Boolean {
|
private fun isEnabled(sourceId: Long): Boolean {
|
||||||
return sourceId.toString() in preferences.disabledSources().get()
|
return sourceId.toString() in preferences.disabledSources().get()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
class ToggleSourcePin(
|
class ToggleSourcePin(
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
|
@ -4,79 +4,13 @@ import androidx.compose.ui.graphics.ImageBitmap
|
|||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class Source(
|
val Source.icon: ImageBitmap?
|
||||||
val id: Long,
|
get() {
|
||||||
val lang: String,
|
return Injekt.get<ExtensionManager>().getAppIconForSource(id)
|
||||||
val name: String,
|
?.toBitmap()
|
||||||
val supportsLatest: Boolean,
|
?.asImageBitmap()
|
||||||
val isStub: Boolean,
|
|
||||||
val pin: Pins = Pins.unpinned,
|
|
||||||
val isUsedLast: Boolean = false,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val visualName: String
|
|
||||||
get() = when {
|
|
||||||
lang.isEmpty() -> name
|
|
||||||
else -> "$name (${lang.uppercase()})"
|
|
||||||
}
|
|
||||||
|
|
||||||
val icon: ImageBitmap?
|
|
||||||
get() {
|
|
||||||
return Injekt.get<ExtensionManager>().getAppIconForSource(id)
|
|
||||||
?.toBitmap()
|
|
||||||
?.asImageBitmap()
|
|
||||||
}
|
|
||||||
|
|
||||||
val key: () -> String = {
|
|
||||||
when {
|
|
||||||
isUsedLast -> "$id-lastused"
|
|
||||||
Pin.Forced in pin -> "$id-forced"
|
|
||||||
else -> "$id"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Pin(val code: Int) {
|
|
||||||
object Unpinned : Pin(0b00)
|
|
||||||
object Pinned : Pin(0b01)
|
|
||||||
object Actual : Pin(0b10)
|
|
||||||
object Forced : Pin(0b100)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun Pins(builder: Pins.PinsBuilder.() -> Unit = {}): Pins {
|
|
||||||
return Pins.PinsBuilder().apply(builder).flags()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Pins(vararg pins: Pin) = Pins {
|
|
||||||
pins.forEach { +it }
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Pins(val code: Int = Pin.Unpinned.code) {
|
|
||||||
|
|
||||||
operator fun contains(pin: Pin): Boolean = pin.code and code == pin.code
|
|
||||||
|
|
||||||
operator fun plus(pin: Pin): Pins = Pins(code or pin.code)
|
|
||||||
|
|
||||||
operator fun minus(pin: Pin): Pins = Pins(code xor pin.code)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val unpinned = Pins(Pin.Unpinned)
|
|
||||||
|
|
||||||
val pinned = Pins(Pin.Pinned, Pin.Actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
class PinsBuilder(var code: Int = 0) {
|
|
||||||
operator fun Pin.unaryPlus() {
|
|
||||||
this@PinsBuilder.code = code or this@PinsBuilder.code
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun Pin.unaryMinus() {
|
|
||||||
this@PinsBuilder.code = code or this@PinsBuilder.code
|
|
||||||
}
|
|
||||||
|
|
||||||
fun flags(): Pins = Pins(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.source.repository
|
package eu.kanade.domain.source.repository
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||||
import eu.kanade.domain.source.model.SourceWithCount
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
|
|
||||||
interface SourceRepository {
|
interface SourceRepository {
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.source.service
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.preference.getEnum
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
|
||||||
class SourcePreferences(
|
class SourcePreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@ -18,8 +18,6 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun duplicatePinnedSources() = preferenceStore.getBoolean("duplicate_pinned_sources", false)
|
|
||||||
|
|
||||||
fun lastUsedSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
fun lastUsedSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
||||||
|
|
||||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.track.interactor
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
|
||||||
class DeleteTrack(
|
class DeleteTrack(
|
||||||
private val trackRepository: TrackRepository,
|
private val trackRepository: TrackRepository,
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
package eu.kanade.domain.track.interactor
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
|
||||||
class GetTracks(
|
class GetTracks(
|
||||||
private val trackRepository: TrackRepository,
|
private val trackRepository: TrackRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
suspend fun awaitOne(id: Long): Track? {
|
||||||
|
return try {
|
||||||
|
trackRepository.getTrackById(id)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun await(mangaId: Long): List<Track> {
|
suspend fun await(mangaId: Long): List<Track> {
|
||||||
return try {
|
return try {
|
||||||
trackRepository.getTracksByMangaId(mangaId)
|
trackRepository.getTracksByMangaId(mangaId)
|
||||||
@ -19,10 +28,6 @@ class GetTracks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Track>> {
|
|
||||||
return trackRepository.getTracksAsFlow()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(mangaId: Long): Flow<List<Track>> {
|
fun subscribe(mangaId: Long): Flow<List<Track>> {
|
||||||
return trackRepository.getTracksByMangaIdAsFlow(mangaId)
|
return trackRepository.getTracksByMangaIdAsFlow(mangaId)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
|
||||||
|
class GetTracksPerManga(
|
||||||
|
private val trackRepository: TrackRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<Map<Long, List<Long>>> {
|
||||||
|
return trackRepository.getTracksAsFlow().map { tracks ->
|
||||||
|
tracks
|
||||||
|
.groupBy { it.mangaId }
|
||||||
|
.mapValues { entry ->
|
||||||
|
entry.value.map { it.syncId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.domain.track.interactor
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.track.model.Track
|
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
|
||||||
class InsertTrack(
|
class InsertTrack(
|
||||||
private val trackRepository: TrackRepository,
|
private val trackRepository: TrackRepository,
|
||||||
|
@ -1,31 +1,16 @@
|
|||||||
package eu.kanade.domain.track.model
|
package eu.kanade.domain.track.model
|
||||||
|
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track as DbTrack
|
import eu.kanade.tachiyomi.data.database.models.Track as DbTrack
|
||||||
|
|
||||||
data class Track(
|
fun Track.copyPersonalFrom(other: Track): Track {
|
||||||
val id: Long,
|
return this.copy(
|
||||||
val mangaId: Long,
|
lastChapterRead = other.lastChapterRead,
|
||||||
val syncId: Long,
|
score = other.score,
|
||||||
val remoteId: Long,
|
status = other.status,
|
||||||
val libraryId: Long?,
|
startDate = other.startDate,
|
||||||
val title: String,
|
finishDate = other.finishDate,
|
||||||
val lastChapterRead: Double,
|
)
|
||||||
val totalChapters: Long,
|
|
||||||
val status: Long,
|
|
||||||
val score: Float,
|
|
||||||
val remoteUrl: String,
|
|
||||||
val startDate: Long,
|
|
||||||
val finishDate: Long,
|
|
||||||
) {
|
|
||||||
fun copyPersonalFrom(other: Track): Track {
|
|
||||||
return this.copy(
|
|
||||||
lastChapterRead = other.lastChapterRead,
|
|
||||||
score = other.score,
|
|
||||||
status = other.status,
|
|
||||||
startDate = other.startDate,
|
|
||||||
finishDate = other.finishDate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
|
fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.job
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
@ -9,14 +9,14 @@ import androidx.work.NetworkType
|
|||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.GetTracks
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -25,36 +25,39 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
|
|||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val getManga = Injekt.get<GetManga>()
|
|
||||||
val getTracks = Injekt.get<GetTracks>()
|
val getTracks = Injekt.get<GetTracks>()
|
||||||
val insertTrack = Injekt.get<InsertTrack>()
|
val insertTrack = Injekt.get<InsertTrack>()
|
||||||
|
|
||||||
val trackManager = Injekt.get<TrackManager>()
|
val trackManager = Injekt.get<TrackManager>()
|
||||||
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||||
|
|
||||||
withIOContext {
|
val results = withIOContext {
|
||||||
val tracks = delayedTrackingStore.getItems().mapNotNull {
|
delayedTrackingStore.getItems()
|
||||||
val manga = getManga.await(it.mangaId) ?: return@withIOContext
|
.mapNotNull {
|
||||||
getTracks.await(manga.id)
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
.find { track -> track.id == it.trackId }
|
if (track == null) {
|
||||||
?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
delayedTrackingStore.remove(it.trackId)
|
||||||
}
|
}
|
||||||
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
tracks.forEach { track ->
|
}
|
||||||
try {
|
.mapNotNull { track ->
|
||||||
val service = trackManager.getService(track.syncId)
|
try {
|
||||||
if (service != null && service.isLogged) {
|
val service = trackManager.getService(track.syncId)
|
||||||
service.update(track.toDbTrack(), true)
|
if (service != null && service.isLogged) {
|
||||||
insertTrack.await(track)
|
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
||||||
|
service.update(track.toDbTrack(), true)
|
||||||
|
insertTrack.await(track)
|
||||||
|
}
|
||||||
|
delayedTrackingStore.remove(track.id)
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
false
|
||||||
}
|
}
|
||||||
delayedTrackingStore.remove(track)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.success()
|
return if (results.isNotEmpty()) Result.failure() else Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.track.service
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class TrackPreferences(
|
class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.domain.track.store
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
|
||||||
|
class DelayedTrackingStore(context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference file where queued tracking updates are stored.
|
||||||
|
*/
|
||||||
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun addItem(track: Track) {
|
||||||
|
val trackId = track.id.toString()
|
||||||
|
val lastChapterRead = preferences.getFloat(trackId, 0f)
|
||||||
|
if (track.lastChapterRead > lastChapterRead) {
|
||||||
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: ${track.lastChapterRead}" }
|
||||||
|
preferences.edit {
|
||||||
|
putFloat(trackId, track.lastChapterRead.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(trackId: Long) {
|
||||||
|
preferences.edit {
|
||||||
|
remove(trackId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<DelayedTrackingItem> {
|
||||||
|
return preferences.all.mapNotNull {
|
||||||
|
DelayedTrackingItem(
|
||||||
|
trackId = it.key.toLong(),
|
||||||
|
lastChapterRead = it.value.toString().toFloat(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DelayedTrackingItem(
|
||||||
|
val trackId: Long,
|
||||||
|
val lastChapterRead: Float,
|
||||||
|
)
|
||||||
|
}
|
@ -4,10 +4,10 @@ import android.os.Build
|
|||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.preference.getEnum
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.domain.updates.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
|
||||||
import eu.kanade.domain.updates.repository.UpdatesRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import java.util.Calendar
|
|
||||||
|
|
||||||
class GetUpdates(
|
|
||||||
private val repository: UpdatesRepository,
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> = subscribe(calendar.time.time)
|
|
||||||
|
|
||||||
fun subscribe(after: Long): Flow<List<UpdatesWithRelations>> {
|
|
||||||
return repository.subscribeAll(after)
|
|
||||||
.onEach { updates ->
|
|
||||||
// Set unread chapter count for bottom bar badge
|
|
||||||
preferences.unreadUpdatesCount().set(updates.count { !it.read })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.components.Badge
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InLibraryBadge(enabled: Boolean) {
|
||||||
|
if (enabled) {
|
||||||
|
Badge(text = stringResource(R.string.in_library))
|
||||||
|
}
|
||||||
|
}
|
@ -1,213 +1,42 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.filled.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material.icons.outlined.Favorite
|
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.FilterChipDefaults
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import eu.kanade.data.source.NoResultsException
|
import eu.kanade.data.source.NoResultsException
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceList
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppStateBanners
|
|
||||||
import eu.kanade.presentation.components.Divider
|
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.EmptyScreenAction
|
import eu.kanade.presentation.components.EmptyScreenAction
|
||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@Composable
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
fun BrowseSourceScreen(
|
import tachiyomi.domain.manga.model.Manga
|
||||||
presenter: BrowseSourcePresenter,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
openFilterSheet: () -> Unit,
|
|
||||||
onMangaClick: (Manga) -> Unit,
|
|
||||||
onMangaLongClick: (Manga) -> Unit,
|
|
||||||
onWebViewClick: () -> Unit,
|
|
||||||
incognitoMode: Boolean,
|
|
||||||
downloadedOnlyMode: Boolean,
|
|
||||||
) {
|
|
||||||
val columns by presenter.getColumnsPreferenceForCurrentOrientation()
|
|
||||||
|
|
||||||
val mangaList = presenter.getMangaList().collectAsLazyPagingItems()
|
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
|
|
||||||
val onHelpClick = {
|
|
||||||
uriHandler.openUri(LocalSource.HELP_URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
|
||||||
BrowseSourceToolbar(
|
|
||||||
state = presenter,
|
|
||||||
source = presenter.source!!,
|
|
||||||
displayMode = presenter.displayMode,
|
|
||||||
onDisplayModeChange = { presenter.displayMode = it },
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = onHelpClick,
|
|
||||||
onSearch = { presenter.search(it) },
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.horizontalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 8.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter == BrowseSourcePresenter.Filter.Popular,
|
|
||||||
onClick = {
|
|
||||||
presenter.reset()
|
|
||||||
presenter.search(GetRemoteManga.QUERY_POPULAR)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Favorite,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.popular))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (presenter.source?.supportsLatest == true) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter == BrowseSourcePresenter.Filter.Latest,
|
|
||||||
onClick = {
|
|
||||||
presenter.reset()
|
|
||||||
presenter.search(GetRemoteManga.QUERY_LATEST)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.NewReleases,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.latest))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (presenter.filters.isNotEmpty()) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter is BrowseSourcePresenter.Filter.UserInput,
|
|
||||||
onClick = openFilterSheet,
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.FilterList,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.action_filter))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
AppStateBanners(downloadedOnlyMode, incognitoMode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
snackbarHost = {
|
|
||||||
SnackbarHost(hostState = snackbarHostState)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
BrowseSourceContent(
|
|
||||||
state = presenter,
|
|
||||||
mangaList = mangaList,
|
|
||||||
getMangaState = { presenter.getManga(it) },
|
|
||||||
columns = columns,
|
|
||||||
displayMode = presenter.displayMode,
|
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
contentPadding = paddingValues,
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
|
||||||
onLocalSourceHelpClick = onHelpClick,
|
|
||||||
onMangaClick = onMangaClick,
|
|
||||||
onMangaLongClick = onMangaLongClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BrowseSourceFloatingActionButton(
|
|
||||||
modifier: Modifier = Modifier.navigationBarsPadding(),
|
|
||||||
isVisible: Boolean,
|
|
||||||
onFabClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(visible = isVisible) {
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
modifier = modifier,
|
|
||||||
text = { Text(text = stringResource(R.string.action_filter)) },
|
|
||||||
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
|
|
||||||
onClick = onFabClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceContent(
|
fun BrowseSourceContent(
|
||||||
state: BrowseSourceState,
|
source: Source?,
|
||||||
mangaList: LazyPagingItems<Manga>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
|
||||||
columns: GridCells,
|
columns: GridCells,
|
||||||
displayMode: LibraryDisplayMode,
|
displayMode: LibraryDisplayMode,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
@ -249,11 +78,11 @@ fun BrowseSourceContent(
|
|||||||
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (state.source is LocalSource) {
|
actions = if (source is LocalSource) {
|
||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -261,17 +90,17 @@ fun BrowseSourceContent(
|
|||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringResId = R.string.action_retry,
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = mangaList::refresh,
|
onClick = mangaList::refresh,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_open_in_web_view,
|
stringResId = R.string.action_open_in_web_view,
|
||||||
icon = Icons.Default.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringResId = R.string.label_help,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -290,7 +119,6 @@ fun BrowseSourceContent(
|
|||||||
LibraryDisplayMode.ComfortableGrid -> {
|
LibraryDisplayMode.ComfortableGrid -> {
|
||||||
BrowseSourceComfortableGrid(
|
BrowseSourceComfortableGrid(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
@ -300,16 +128,14 @@ fun BrowseSourceContent(
|
|||||||
LibraryDisplayMode.List -> {
|
LibraryDisplayMode.List -> {
|
||||||
BrowseSourceList(
|
BrowseSourceList(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
onMangaLongClick = onMangaLongClick,
|
onMangaLongClick = onMangaLongClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||||
BrowseSourceCompactGrid(
|
BrowseSourceCompactGrid(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
@ -318,3 +144,24 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MissingSourceScreen(
|
||||||
|
source: SourceManager.StubSource,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = source.name,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
EmptyScreen(
|
||||||
|
message = source.getSourceNotInstalledException().message!!,
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Filter
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
interface BrowseSourceState {
|
|
||||||
val source: CatalogueSource?
|
|
||||||
var searchQuery: String?
|
|
||||||
val currentFilter: Filter
|
|
||||||
val isUserQuery: Boolean
|
|
||||||
val filters: FilterList
|
|
||||||
val filterItems: List<IFlexible<*>>
|
|
||||||
var dialog: BrowseSourcePresenter.Dialog?
|
|
||||||
}
|
|
||||||
|
|
||||||
fun BrowseSourceState(initialQuery: String?): BrowseSourceState {
|
|
||||||
return when (val filter = Filter.valueOf(initialQuery ?: "")) {
|
|
||||||
Filter.Latest, Filter.Popular -> BrowseSourceStateImpl(initialCurrentFilter = filter)
|
|
||||||
is Filter.UserInput -> BrowseSourceStateImpl(initialQuery = initialQuery, initialCurrentFilter = filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowseSourceStateImpl(initialQuery: String? = null, initialCurrentFilter: Filter) : BrowseSourceState {
|
|
||||||
override var source: CatalogueSource? by mutableStateOf(null)
|
|
||||||
override var searchQuery: String? by mutableStateOf(initialQuery)
|
|
||||||
override var currentFilter: Filter by mutableStateOf(initialCurrentFilter)
|
|
||||||
override val isUserQuery: Boolean by derivedStateOf { currentFilter is Filter.UserInput && currentFilter.query.isNotEmpty() }
|
|
||||||
override var filters: FilterList by mutableStateOf(FilterList())
|
|
||||||
override val filterItems: List<IFlexible<*>> by derivedStateOf { filters.toItems() }
|
|
||||||
override var dialog: BrowseSourcePresenter.Dialog? by mutableStateOf(null)
|
|
||||||
}
|
|
@ -4,12 +4,9 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -26,7 +23,6 @@ import androidx.compose.material.icons.outlined.History
|
|||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -42,38 +38,43 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.DIVIDER_ALPHA
|
import eu.kanade.presentation.components.DIVIDER_ALPHA
|
||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
|
import eu.kanade.presentation.util.padding
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsPresenter
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsState
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionDetailsScreen(
|
fun ExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
presenter: ExtensionDetailsPresenter,
|
state: ExtensionDetailsState,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
|
onClickWhatsNew: () -> Unit,
|
||||||
|
onClickReadme: () -> Unit,
|
||||||
|
onClickEnableAll: () -> Unit,
|
||||||
|
onClickDisableAll: () -> Unit,
|
||||||
|
onClickClearCookies: () -> Unit,
|
||||||
|
onClickUninstall: () -> Unit,
|
||||||
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
@ -82,19 +83,19 @@ fun ExtensionDetailsScreen(
|
|||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = buildList {
|
||||||
if (presenter.extension?.isUnofficial == false) {
|
if (state.extension?.isUnofficial == false) {
|
||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.whats_new),
|
title = stringResource(R.string.whats_new),
|
||||||
icon = Icons.Outlined.History,
|
icon = Icons.Outlined.History,
|
||||||
onClick = { uriHandler.openUri(presenter.getChangelogUrl()) },
|
onClick = onClickWhatsNew,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
title = stringResource(R.string.action_faq_and_guides),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = { uriHandler.openUri(presenter.getReadmeUrl()) },
|
onClick = onClickReadme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -102,15 +103,15 @@ fun ExtensionDetailsScreen(
|
|||||||
listOf(
|
listOf(
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_enable_all),
|
title = stringResource(R.string.action_enable_all),
|
||||||
onClick = { presenter.toggleSources(true) },
|
onClick = onClickEnableAll,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.action_disable_all),
|
title = stringResource(R.string.action_disable_all),
|
||||||
onClick = { presenter.toggleSources(false) },
|
onClick = onClickDisableAll,
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
title = stringResource(R.string.pref_clear_cookies),
|
||||||
onClick = { presenter.clearCookies() },
|
onClick = onClickClearCookies,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -121,93 +122,84 @@ fun ExtensionDetailsScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
ExtensionDetails(paddingValues, presenter, onClickSourcePreferences)
|
if (state.extension == null) {
|
||||||
|
EmptyScreen(
|
||||||
|
textResource = R.string.empty_screen,
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionDetails(
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
extension = state.extension,
|
||||||
|
sources = state.sources,
|
||||||
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
|
onClickUninstall = onClickUninstall,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionDetails(
|
private fun ExtensionDetails(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
presenter: ExtensionDetailsPresenter,
|
extension: Extension.Installed,
|
||||||
|
sources: List<ExtensionSourceItem>,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
|
onClickUninstall: () -> Unit,
|
||||||
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
val context = LocalContext.current
|
||||||
presenter.isLoading -> LoadingScreen()
|
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||||
presenter.extension == null -> EmptyScreen(
|
|
||||||
textResource = R.string.empty_screen,
|
|
||||||
modifier = Modifier.padding(contentPadding),
|
|
||||||
)
|
|
||||||
else -> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val extension = presenter.extension
|
|
||||||
var showNsfwWarning by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
when {
|
|
||||||
extension.isUnofficial ->
|
|
||||||
item {
|
|
||||||
WarningBanner(R.string.unofficial_extension_message)
|
|
||||||
}
|
|
||||||
extension.isObsolete ->
|
|
||||||
item {
|
|
||||||
WarningBanner(R.string.obsolete_extension_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ScrollbarLazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
extension.isUnofficial ->
|
||||||
item {
|
item {
|
||||||
DetailsHeader(
|
WarningBanner(R.string.unofficial_extension_message)
|
||||||
extension = extension,
|
|
||||||
onClickUninstall = { presenter.uninstallExtension() },
|
|
||||||
onClickAppInfo = {
|
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
||||||
data = Uri.fromParts("package", extension.pkgName, null)
|
|
||||||
context.startActivity(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClickAgeRating = {
|
|
||||||
showNsfwWarning = true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
extension.isObsolete ->
|
||||||
|
item {
|
||||||
|
WarningBanner(R.string.obsolete_extension_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items(
|
item {
|
||||||
items = presenter.sources,
|
DetailsHeader(
|
||||||
key = { it.source.id },
|
extension = extension,
|
||||||
) { source ->
|
onClickUninstall = onClickUninstall,
|
||||||
SourceSwitchPreference(
|
onClickAppInfo = {
|
||||||
modifier = Modifier.animateItemPlacement(),
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
source = source,
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
context.startActivity(this)
|
||||||
onClickSource = { presenter.toggleSource(it) },
|
}
|
||||||
)
|
},
|
||||||
}
|
onClickAgeRating = {
|
||||||
}
|
showNsfwWarning = true
|
||||||
if (showNsfwWarning) {
|
},
|
||||||
NsfwWarningDialog(
|
)
|
||||||
onClickConfirm = {
|
}
|
||||||
showNsfwWarning = false
|
|
||||||
},
|
items(
|
||||||
)
|
items = sources,
|
||||||
}
|
key = { it.source.id },
|
||||||
|
) { source ->
|
||||||
|
SourceSwitchPreference(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
source = source,
|
||||||
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (showNsfwWarning) {
|
||||||
|
NsfwWarningDialog(
|
||||||
@Composable
|
onClickConfirm = {
|
||||||
private fun WarningBanner(@StringRes textRes: Int) {
|
showNsfwWarning = false
|
||||||
Box(
|
},
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.error)
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(textRes),
|
|
||||||
color = MaterialTheme.colorScheme.onError,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,10 +218,10 @@ private fun DetailsHeader(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
start = horizontalPadding,
|
start = MaterialTheme.padding.medium,
|
||||||
end = horizontalPadding,
|
end = MaterialTheme.padding.medium,
|
||||||
top = 16.dp,
|
top = MaterialTheme.padding.medium,
|
||||||
bottom = 8.dp,
|
bottom = MaterialTheme.padding.small,
|
||||||
),
|
),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
@ -243,6 +235,7 @@ private fun DetailsHeader(
|
|||||||
Text(
|
Text(
|
||||||
text = extension.name,
|
text = extension.name,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
|
||||||
val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
|
val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
|
||||||
@ -257,8 +250,8 @@ private fun DetailsHeader(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = horizontalPadding * 2,
|
horizontal = MaterialTheme.padding.extraLarge,
|
||||||
vertical = 8.dp,
|
vertical = MaterialTheme.padding.small,
|
||||||
),
|
),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@ -295,10 +288,10 @@ private fun DetailsHeader(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
start = horizontalPadding,
|
start = MaterialTheme.padding.medium,
|
||||||
end = horizontalPadding,
|
end = MaterialTheme.padding.medium,
|
||||||
top = 8.dp,
|
top = MaterialTheme.padding.small,
|
||||||
bottom = 16.dp,
|
bottom = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
@ -380,15 +373,14 @@ private fun SourceSwitchPreference(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
PreferenceRow(
|
TextPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = if (source.labelAsName) {
|
title = if (source.labelAsName) {
|
||||||
source.source.toString()
|
source.source.toString()
|
||||||
} else {
|
} else {
|
||||||
LocaleHelper.getSourceDisplayName(source.source.lang, context)
|
LocaleHelper.getSourceDisplayName(source.source.lang, context)
|
||||||
},
|
},
|
||||||
onClick = { onClickSource(source.source.id) },
|
widget = {
|
||||||
action = {
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@ -402,9 +394,14 @@ private fun SourceSwitchPreference(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch(checked = source.enabled, onCheckedChange = null)
|
Switch(
|
||||||
|
checked = source.enabled,
|
||||||
|
onCheckedChange = null,
|
||||||
|
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onPreferenceClick = { onClickSource(source.source.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
interface ExtensionDetailsState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val extension: Extension.Installed?
|
|
||||||
val sources: List<ExtensionSourceItem>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExtensionDetailsState(): ExtensionDetailsState {
|
|
||||||
return ExtensionDetailsStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExtensionDetailsStateImpl : ExtensionDetailsState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var extension: Extension.Installed? by mutableStateOf(null)
|
|
||||||
override var sources: List<ExtensionSourceItem> by mutableStateOf(emptyList())
|
|
||||||
}
|
|
@ -3,30 +3,25 @@ package eu.kanade.presentation.browse
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionFilterScreen(
|
fun ExtensionFilterScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
presenter: ExtensionFilterPresenter,
|
state: ExtensionFilterState.Success,
|
||||||
|
onClickToggle: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
@ -36,69 +31,38 @@ fun ExtensionFilterScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
if (state.isEmpty) {
|
||||||
presenter.isLoading -> LoadingScreen()
|
EmptyScreen(
|
||||||
presenter.isEmpty -> EmptyScreen(
|
|
||||||
textResource = R.string.empty_screen,
|
textResource = R.string.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else -> {
|
return@Scaffold
|
||||||
SourceFilterContent(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
state = presenter,
|
|
||||||
onClickLang = {
|
|
||||||
presenter.toggleLanguage(it)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
presenter.events.collectLatest {
|
|
||||||
when (it) {
|
|
||||||
ExtensionFilterPresenter.Event.FailedFetchingLanguages -> {
|
|
||||||
context.toast(R.string.internal_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ExtensionFilterContent(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
state = state,
|
||||||
|
onClickLang = onClickToggle,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceFilterContent(
|
private fun ExtensionFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: ExtensionFilterState,
|
state: ExtensionFilterState.Success,
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
val context = LocalContext.current
|
||||||
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(
|
items(state.languages) { language ->
|
||||||
items = state.items,
|
SwitchPreferenceWidget(
|
||||||
) { model ->
|
|
||||||
ExtensionFilterItem(
|
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
lang = model.lang,
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
enabled = model.enabled,
|
checked = language in state.enabledLanguages,
|
||||||
onClickItem = onClickLang,
|
onCheckedChanged = { onClickLang(language) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ExtensionFilterItem(
|
|
||||||
modifier: Modifier,
|
|
||||||
lang: String,
|
|
||||||
enabled: Boolean,
|
|
||||||
onClickItem: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
PreferenceRow(
|
|
||||||
modifier = modifier,
|
|
||||||
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
|
|
||||||
action = {
|
|
||||||
Switch(checked = enabled, onCheckedChange = null)
|
|
||||||
},
|
|
||||||
onClick = { onClickItem(lang) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.FilterUiModel
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
interface ExtensionFilterState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val items: List<FilterUiModel>
|
|
||||||
val isEmpty: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExtensionFilterState(): ExtensionFilterState {
|
|
||||||
return ExtensionFilterStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExtensionFilterStateImpl : ExtensionFilterState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var items: List<FilterUiModel> by mutableStateOf(emptyList())
|
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@ -40,24 +40,25 @@ import eu.kanade.presentation.browse.components.ExtensionIcon
|
|||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.SwipeRefresh
|
import eu.kanade.presentation.components.PullRefresh
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.padding
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
import eu.kanade.presentation.util.topPaddingValues
|
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionScreen(
|
fun ExtensionScreen(
|
||||||
presenter: ExtensionsPresenter,
|
state: ExtensionsState,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
|
searchQuery: String?,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
@ -68,20 +69,27 @@ fun ExtensionScreen(
|
|||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
) {
|
) {
|
||||||
SwipeRefresh(
|
PullRefresh(
|
||||||
refreshing = presenter.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !presenter.isLoading,
|
enabled = !state.isLoading,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
presenter.isLoading -> LoadingScreen()
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
presenter.isEmpty -> EmptyScreen(
|
state.isEmpty -> {
|
||||||
textResource = R.string.empty_screen,
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
modifier = Modifier.padding(contentPadding),
|
R.string.no_results_found
|
||||||
)
|
} else {
|
||||||
|
R.string.empty_screen
|
||||||
|
}
|
||||||
|
EmptyScreen(
|
||||||
|
textResource = msg,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
ExtensionContent(
|
ExtensionContent(
|
||||||
state = presenter,
|
state = state,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
@ -113,81 +121,77 @@ private fun ExtensionContent(
|
|||||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding + topPaddingValues,
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
state.items.forEach { (header, items) ->
|
||||||
items = state.items,
|
item(
|
||||||
contentType = {
|
contentType = "header",
|
||||||
when (it) {
|
key = "extensionHeader-${header.hashCode()}",
|
||||||
is ExtensionUiModel.Header -> "header"
|
) {
|
||||||
is ExtensionUiModel.Item -> "item"
|
when (header) {
|
||||||
}
|
is ExtensionUiModel.Header.Resource -> {
|
||||||
},
|
val action: @Composable RowScope.() -> Unit =
|
||||||
key = {
|
if (header.textRes == R.string.ext_updates_pending) {
|
||||||
when (it) {
|
{
|
||||||
is ExtensionUiModel.Header -> "extensionHeader-${it.hashCode()}"
|
Button(onClick = { onClickUpdateAll() }) {
|
||||||
is ExtensionUiModel.Item -> "extension-${it.hashCode()}"
|
Text(
|
||||||
}
|
text = stringResource(R.string.ext_update_all),
|
||||||
},
|
style = LocalTextStyle.current.copy(
|
||||||
) { item ->
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
when (item) {
|
),
|
||||||
is ExtensionUiModel.Header.Resource -> {
|
)
|
||||||
val action: @Composable RowScope.() -> Unit =
|
|
||||||
if (item.textRes == R.string.ext_updates_pending) {
|
|
||||||
{
|
|
||||||
Button(onClick = { onClickUpdateAll() }) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.ext_update_all),
|
|
||||||
style = LocalTextStyle.current.copy(
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
ExtensionHeader(
|
|
||||||
textRes = item.textRes,
|
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
action = action,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ExtensionUiModel.Header.Text -> {
|
|
||||||
ExtensionHeader(
|
|
||||||
text = item.text,
|
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ExtensionUiModel.Item -> {
|
|
||||||
ExtensionItem(
|
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
item = item,
|
|
||||||
onClickItem = {
|
|
||||||
when (it) {
|
|
||||||
is Extension.Available -> onInstallExtension(it)
|
|
||||||
is Extension.Installed -> onOpenExtension(it)
|
|
||||||
is Extension.Untrusted -> { trustState = it }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongClickItem = onLongClickItem,
|
|
||||||
onClickItemCancel = onClickItemCancel,
|
|
||||||
onClickItemAction = {
|
|
||||||
when (it) {
|
|
||||||
is Extension.Available -> onInstallExtension(it)
|
|
||||||
is Extension.Installed -> {
|
|
||||||
if (it.hasUpdate) {
|
|
||||||
onUpdateExtension(it)
|
|
||||||
} else {
|
|
||||||
onOpenExtension(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Extension.Untrusted -> { trustState = it }
|
} else {
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
},
|
ExtensionHeader(
|
||||||
)
|
textRes = header.textRes,
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
action = action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ExtensionUiModel.Header.Text -> {
|
||||||
|
ExtensionHeader(
|
||||||
|
text = header.text,
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
contentType = { "item" },
|
||||||
|
key = { "extension-${it.hashCode()}" },
|
||||||
|
) { item ->
|
||||||
|
ExtensionItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
item = item,
|
||||||
|
onClickItem = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onInstallExtension(it)
|
||||||
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
|
is Extension.Untrusted -> { trustState = it }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onClickItemAction = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onInstallExtension(it)
|
||||||
|
is Extension.Installed -> {
|
||||||
|
if (it.hasUpdate) {
|
||||||
|
onUpdateExtension(it)
|
||||||
|
} else {
|
||||||
|
onOpenExtension(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Untrusted -> { trustState = it }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trustState != null) {
|
if (trustState != null) {
|
||||||
@ -272,7 +276,7 @@ private fun ExtensionItemContent(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.padding(start = horizontalPadding),
|
modifier = modifier.padding(start = MaterialTheme.padding.medium),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = extension.name,
|
text = extension.name,
|
||||||
@ -368,7 +372,7 @@ private fun ExtensionItemActions(
|
|||||||
} else {
|
} else {
|
||||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
contentDescription = stringResource(R.string.action_cancel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -396,7 +400,7 @@ private fun ExtensionHeader(
|
|||||||
action: @Composable RowScope.() -> Unit = {},
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.padding(horizontal = horizontalPadding),
|
modifier = modifier.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
|
||||||
|
|
||||||
interface ExtensionsState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val isRefreshing: Boolean
|
|
||||||
val items: List<ExtensionUiModel>
|
|
||||||
val updates: Int
|
|
||||||
val isEmpty: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExtensionState(): ExtensionsState {
|
|
||||||
return ExtensionsStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExtensionsStateImpl : ExtensionsState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var isRefreshing: Boolean by mutableStateOf(false)
|
|
||||||
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
|
|
||||||
override var updates: Int by mutableStateOf(0)
|
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
|
||||||
}
|
|
@ -0,0 +1,112 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.padding
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchScreen(
|
||||||
|
state: GlobalSearchState,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
GlobalSearchToolbar(
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
progress = state.progress,
|
||||||
|
total = state.total,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
onSearch = onSearch,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
GlobalSearchContent(
|
||||||
|
items = state.items,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
getManga = getManga,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchContent(
|
||||||
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
items.forEach { (source, result) ->
|
||||||
|
item(key = source.id) {
|
||||||
|
GlobalSearchResultItem(
|
||||||
|
title = source.name,
|
||||||
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
|
onClick = { onClickSource(source) },
|
||||||
|
) {
|
||||||
|
when (result) {
|
||||||
|
SearchItemResult.Loading -> {
|
||||||
|
GlobalSearchLoadingResultItem()
|
||||||
|
}
|
||||||
|
is SearchItemResult.Success -> {
|
||||||
|
if (result.isEmpty) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_results_found),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return@GlobalSearchResultItem
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalSearchCardRow(
|
||||||
|
titles = result.result,
|
||||||
|
getManga = { getManga(source, it) },
|
||||||
|
onClick = onClickItem,
|
||||||
|
onLongClick = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SearchItemResult.Error -> {
|
||||||
|
GlobalSearchErrorResultItem(message = result.throwable.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,31 +4,24 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter.Event
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateMangaScreen(
|
fun MigrateMangaScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
title: String?,
|
title: String?,
|
||||||
presenter: MigrateMangaPresenter,
|
state: MigrateMangaState,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
@ -38,30 +31,20 @@ fun MigrateMangaScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
if (state.isEmpty) {
|
||||||
presenter.isLoading -> LoadingScreen()
|
EmptyScreen(
|
||||||
presenter.isEmpty -> EmptyScreen(
|
|
||||||
textResource = R.string.empty_screen,
|
textResource = R.string.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else -> {
|
return@Scaffold
|
||||||
MigrateMangaContent(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
state = presenter,
|
|
||||||
onClickItem = onClickItem,
|
|
||||||
onClickCover = onClickCover,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
presenter.events.collectLatest { event ->
|
|
||||||
when (event) {
|
|
||||||
Event.FailedFetchingFavorites -> {
|
|
||||||
context.toast(R.string.internal_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MigrateMangaContent(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
state = state,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onClickCover = onClickCover,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +55,10 @@ private fun MigrateMangaContent(
|
|||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.items) { manga ->
|
items(state.titles) { manga ->
|
||||||
MigrateMangaItem(
|
MigrateMangaItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
|
|
||||||
interface MigrateMangaState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val items: List<Manga>
|
|
||||||
val isEmpty: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MigrationMangaState(): MigrateMangaState {
|
|
||||||
return MigrateMangaStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class MigrateMangaStateImpl : MigrateMangaState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var items: List<Manga> by mutableStateOf(emptyList())
|
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
|
||||||
}
|
|
@ -0,0 +1,101 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSearchScreen(
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
state: MigrateSearchState,
|
||||||
|
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
GlobalSearchToolbar(
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
progress = state.progress,
|
||||||
|
total = state.total,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
onSearch = onSearch,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
MigrateSearchContent(
|
||||||
|
sourceId = state.manga?.source ?: -1,
|
||||||
|
items = state.items,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
getManga = getManga,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSearchContent(
|
||||||
|
sourceId: Long,
|
||||||
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
items.forEach { (source, result) ->
|
||||||
|
item(key = source.id) {
|
||||||
|
GlobalSearchResultItem(
|
||||||
|
title = if (source.id == sourceId) "▶ ${source.name}" else source.name,
|
||||||
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
|
onClick = { onClickSource(source) },
|
||||||
|
) {
|
||||||
|
when (result) {
|
||||||
|
SearchItemResult.Loading -> {
|
||||||
|
GlobalSearchLoadingResultItem()
|
||||||
|
}
|
||||||
|
is SearchItemResult.Success -> {
|
||||||
|
if (result.isEmpty) {
|
||||||
|
GlobalSearchEmptyResultItem()
|
||||||
|
return@GlobalSearchResultItem
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalSearchCardRow(
|
||||||
|
titles = result.result,
|
||||||
|
getManga = { getManga(source, it) },
|
||||||
|
onClick = onClickItem,
|
||||||
|
onLongClick = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SearchItemResult.Error -> {
|
||||||
|
GlobalSearchErrorResultItem(message = result.throwable.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,9 +22,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.presentation.components.Badge
|
||||||
@ -34,40 +32,43 @@ import eu.kanade.presentation.components.LoadingScreen
|
|||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.padding
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
import eu.kanade.presentation.util.topPaddingValues
|
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
presenter: MigrationSourcesPresenter,
|
state: MigrateSourceState,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
onToggleSortingMode: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
when {
|
when {
|
||||||
presenter.isLoading -> LoadingScreen()
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
presenter.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
textResource = R.string.information_empty_library,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else ->
|
else ->
|
||||||
MigrateSourceList(
|
MigrateSourceList(
|
||||||
list = presenter.items,
|
list = state.items,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = { source ->
|
onLongClickItem = { source ->
|
||||||
val sourceId = source.id.toString()
|
val sourceId = source.id.toString()
|
||||||
context.copyToClipboard(sourceId, sourceId)
|
context.copyToClipboard(sourceId, sourceId)
|
||||||
},
|
},
|
||||||
sortingMode = presenter.sortingMode,
|
sortingMode = state.sortingMode,
|
||||||
onToggleSortingMode = { presenter.toggleSortingMode() },
|
onToggleSortingMode = onToggleSortingMode,
|
||||||
sortingDirection = presenter.sortingDirection,
|
sortingDirection = state.sortingDirection,
|
||||||
onToggleSortingDirection = { presenter.toggleSortingDirection() },
|
onToggleSortingDirection = onToggleSortingDirection,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,13 +85,13 @@ private fun MigrateSourceList(
|
|||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
contentPadding = contentPadding + topPaddingValues,
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
) {
|
) {
|
||||||
stickyHeader(key = STICKY_HEADER_KEY_PREFIX) {
|
stickyHeader(key = STICKY_HEADER_KEY_PREFIX) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.padding(start = horizontalPadding),
|
.padding(start = MaterialTheme.padding.medium),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -152,7 +153,7 @@ private fun MigrateSourceItem(
|
|||||||
content = { _, sourceLangString ->
|
content = { _, sourceLangString ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = horizontalPadding)
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -162,7 +163,7 @@ private fun MigrateSourceItem(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
if (sourceLangString != null) {
|
if (sourceLangString != null) {
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
|
|
||||||
interface MigrateSourceState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val items: List<Pair<Source, Long>>
|
|
||||||
val isEmpty: Boolean
|
|
||||||
val sortingMode: SetMigrateSorting.Mode
|
|
||||||
val sortingDirection: SetMigrateSorting.Direction
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MigrateSourceState(): MigrateSourceState {
|
|
||||||
return MigrateSourceStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class MigrateSourceStateImpl : MigrateSourceState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
|
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
|
||||||
override var sortingMode: SetMigrateSorting.Mode by mutableStateOf(SetMigrateSorting.Mode.ALPHABETICAL)
|
|
||||||
override var sortingDirection: SetMigrateSorting.Direction by mutableStateOf(SetMigrateSorting.Direction.ASCENDING)
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user