mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-28 10:25:53 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
0cffb9e503 |
.editorconfig.gitattributes
.github
CONTRIBUTING.mdFUNDING.ymlISSUE_TEMPLATE.md
.gitignore.travis.ymlCODE_OF_CONDUCT.mdCONTRIBUTING.mdLICENSEREADME.mdISSUE_TEMPLATE
mergify.ymlpull_request_template.mdreadme-images
renovate.jsonworkflows
app
.gitignorebuild.gradlebuild.gradle.ktsproguard-android-optimize.txtproguard-rules.proshortcuts.xml
build.gradlebuild.gradle.ktssrc
debug
res
drawable
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
main
AndroidManifest.xml
assets
baseline-prof.txtic_launcher-web.pngjava
eu
kanade
core
prefs
util
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.ktGetRemoteManga.ktGetSourcesWithFavoriteCount.ktGetSourcesWithNonLibraryManga.ktSetMigrateSorting.ktToggleLanguage.ktToggleSource.ktToggleSourcePin.kt
model
repository
service
track
interactor
model
service
store
ui
presentation
browse
BrowseBadges.ktBrowseSourceScreen.ktExtensionDetailsScreen.ktExtensionFilterScreen.ktExtensionsScreen.ktGlobalSearchScreen.ktMigrateMangaScreen.ktMigrateSearchScreen.ktMigrateSourceScreen.ktSourcesFilterScreen.ktSourcesScreen.kt
components
category
components
AdaptiveSheet.ktAlertDialog.ktAppBar.ktBadges.ktBanners.ktButton.ktChangeCategoryDialog.ktChapterDownloadIndicator.ktCommonMangaItem.ktDeleteLibraryMangaDialog.ktDivider.ktDownloadDropdownMenu.ktDropdownMenu.ktDuplicateMangaDialog.ktEmptyScreen.ktFloatingActionButton.ktIconButton.ktInfoScaffold.ktLazyGrid.ktLazyList.ktLinkIcon.ktListGroupHeader.ktLoadingScreen.ktMangaBottomActionMenu.ktMangaCover.ktNavigationBar.ktNavigationRail.ktPager.ktPill.ktPullRefresh.ktRelativeDateHeader.ktScaffold.ktSettingsItems.ktSurface.ktTabbedDialog.ktTabbedScreen.ktTabs.ktTrackLogoIcon.ktTwoPanelBox.ktVerticalFastScroller.kt
crash
history
library
components
manga
ChapterSettingsDialog.ktMangaScreen.ktMangaScreenConstants.ktTrackInfoDialogHome.ktTrackInfoDialogSelector.ktTrackServiceSearch.kt
components
more
LogoHeader.ktMoreScreen.ktNewUpdateScreen.kt
settings
PreferenceItem.ktPreferenceModel.ktPreferenceScaffold.ktPreferenceScreen.kt
screen
AboutScreen.ktClearDatabaseScreen.ktCommons.ktLicensesScreen.ktSearchableSettings.ktSettingsAdvancedScreen.ktSettingsAppearanceScreen.ktSettingsBackupScreen.ktSettingsBrowseScreen.ktSettingsDownloadScreen.ktSettingsGeneralScreen.ktSettingsLibraryScreen.ktSettingsMainScreen.ktSettingsReaderScreen.ktSettingsSearchScreen.ktSettingsSecurityScreen.ktSettingsTrackingScreen.ktWorkerInfoScreen.kt
widget
stats
theme
updates
util
Constants.ktElevation.ktLazyListState.ktModifier.ktNavigator.ktPaddingValues.ktPreference.ktPreview.ktResources.ktScrollable.ktScrollbar.ktWindowSize.kt
webview
tachiyomi
App.ktAppInfo.ktAppModule.ktMigrations.kt
crash
data
backup
BackupConst.ktBackupCreatorJob.ktBackupFileValidator.ktBackupManager.ktBackupNotifier.ktBackupRestoreService.ktBackupRestorer.kt
models
Backup.ktBackupCategory.ktBackupChapter.ktBackupHistory.ktBackupManga.ktBackupSerializer.ktBackupSource.ktBackupTracking.kt
serializer
cache
coil
database
DatabaseHelper.ktDbOpenHelper.ktRawQueries.kt
models
Category.javaChapter.javaChapter.ktChapterImpl.ktManga.javaManga.ktMangaCategory.javaMangaChapter.javaMangaImpl.ktMangaSync.javaTrack.ktTrackImpl.kt
resolvers
tables
download
DownloadCache.ktDownloadManager.ktDownloadNotifier.ktDownloadPendingDeleter.ktDownloadProvider.ktDownloadService.ktDownloadStore.ktDownloader.kt
model
library
mangasync
network
notification
preference
saver
source
track
EnhancedTrackService.ktTrackManager.ktTrackService.kt
anilist
bangumi
kavita
kitsu
komga
mangaupdates
model
myanimelist
shikimori
suwayomi
updater
event
extension
injection
source
ui
backup
base
activity
adapter
FlexibleViewHolder.javaItemTouchHelperAdapter.javaOnStartDragListener.javaSimpleItemTouchHelperCallback.javaSmartFragmentStatePagerAdapter.java
decoration
delegate
fab
fragment
listener
presenter
browse
BrowseTab.kt
extension
ExtensionFilterScreen.ktExtensionFilterScreenModel.ktExtensionsScreenModel.ktExtensionsTab.kt
details
migration
source
catalogue
CatalogueAdapter.ktCatalogueFragment.ktCatalogueGridHolder.ktCatalogueHolder.ktCatalogueListHolder.ktCataloguePresenter.kt
category
CategoryActivity.ktCategoryAdapter.ktCategoryHolder.ktCategoryItemTouchHelper.ktCategoryPresenter.ktCategoryScreen.ktCategoryScreenModel.kt
download
DownloadAdapter.ktDownloadFragment.ktDownloadHeaderHolder.ktDownloadHeaderItem.ktDownloadHolder.ktDownloadItem.ktDownloadPresenter.ktDownloadQueueScreen.ktDownloadQueueScreenModel.kt
history
home
library
LibraryAdapter.ktLibraryCategoryAdapter.ktLibraryCategoryFragment.ktLibraryFragment.ktLibraryHolder.ktLibraryItem.ktLibraryPresenter.ktLibraryScreenModel.ktLibrarySettingsSheet.ktLibraryTab.kt
main
manga
MangaActivity.ktMangaCoverScreenModel.ktMangaPresenter.ktMangaScreen.ktMangaScreenModel.kt
chapter
info
myanimelist
MyAnimeListDialogFragment.ktMyAnimeListFragment.ktMyAnimeListPresenter.ktMyAnimeListSearchAdapter.kt
track
more
reader
PageIndicatorTextView.ktReaderActivity.ktReaderColorFilterView.ktReaderNavigationOverlayView.ktReaderPageSheet.ktReaderPopupMenu.ktReaderPresenter.ktReaderSlider.ktReaderViewModel.ktSaveImageNotifier.kt
loader
ChapterLoader.ktDirectoryPageLoader.ktDownloadPageLoader.ktEpubPageLoader.ktHttpPageLoader.ktPageLoader.ktRarPageLoader.ktZipPageLoader.kt
model
setting
OrientationType.ktReaderColorFilterSettings.ktReaderGeneralSettings.ktReaderPreferences.ktReaderReadingModeSettings.ktReaderSettingsSheet.ktReadingModeType.kt
viewer
BaseViewer.ktGestureDetectorWithLongTap.ktMissingChapters.ktReaderButton.ktReaderPageImageView.ktReaderProgressIndicator.ktReaderTransitionView.ktViewerConfig.ktViewerNavigation.kt
base
navigation
pager
OnChapterBoundariesOutListener.ktPager.javaPager.ktPagerConfig.ktPagerPageHolder.ktPagerReader.ktPagerReaderAdapter.ktPagerReaderFragment.ktPagerTransitionHolder.ktPagerViewer.ktPagerViewerAdapter.ktPagerViewers.kt
horizontal
vertical
webtoon
recent
RecentChaptersAdapter.ktRecentChaptersFragment.ktRecentChaptersHolder.ktRecentChaptersPresenter.ktSectionViewHolder.kt
security
setting
SettingsAboutFragment.ktSettingsActivity.ktSettingsAdvancedFragment.ktSettingsDownloadsFragment.ktSettingsGeneralFragment.ktSettingsNestedFragment.ktSettingsScreen.ktSettingsSourcesFragment.ktSettingsSyncFragment.kt
track
stats
updates
webview
util
AndroidComponentUtil.javaBackupUtil.ktChapterRecognition.javaContextExtensions.ktCrashLogUtil.ktDeviceUtil.ktDiskUtils.javaDynamicConcurrentMergeOperator.javaGLUtil.javaImageViewExtensions.ktJsoupExtensions.ktMangaExtensions.ktOkioExtensions.ktParser.javaPkceUtil.ktRxPager.ktSharedData.ktThemeExtensions.ktUrlUtil.javaViewExtensions.ktViewGroupExtensions.kt
chapter
lang
CloseableExtensions.ktDateExtensions.ktHash.ktRectFExtensions.ktRetryWithDelay.ktRxExtensions.ktStringExtensions.kt
preference
storage
system
ActivityExtensions.ktAnimationExtensions.ktAuthenticatorUtil.ktBuildConfig.ktContextExtensions.ktDeviceUtilExtensions.ktGLUtil.ktImageUtil.ktIntentExtensions.ktInternalResourceHelper.ktLocaleHelper.ktNotificationExtensions.kt
view
widget
AutofitRecyclerView.ktEndlessGridScrollListener.javaEndlessListScrollListener.javaEndlessScrollListener.javaExtendedNavigationView.ktMaterialFastScroll.ktMaterialSpinnerView.ktMinMaxNumberPicker.ktNpaGridLayoutManager.ktNpaLinearLayoutManager.ktOutlineSpan.ktPTSansTextView.ktPreCachingLayoutManager.ktRevealAnimationView.ktSimpleNavigationView.ktTachiyomiTextInputEditText.ktViewPagerAdapter.kt
listener
preference
IntListPreference.ktLibraryColumnsDialog.ktLoginDialogPreference.ktLoginPreference.ktMangaSyncLoginDialog.ktSimpleDialogPreference.ktSourceLoginDialog.kt
sheet
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
bottom_sheet_slide_in.xmlbottom_sheet_slide_out.xmlenter_from_left.xmlenter_from_right.xmlexit_to_left.xmlexit_to_right.xmlfab_hide_to_bottom.xmlfab_show_from_bottom.xmlfade_in.xmlshared_axis_x_pop_enter.xmlshared_axis_x_pop_exit.xmlshared_axis_x_push_enter.xmlshared_axis_x_push_exit.xml
color
abc_primary_text_material_dark.xmldraggable_card_foreground.xmlripple_toolbar_fainter.xmlslider_active_track.xmlslider_inactive_track.xml
drawable-hdpi
application_logo_144dp.pngic_clear_grey_24dp_img.pngic_refresh_grey_24dp_img.pngic_refresh_white_24dp_img.pngic_system_update_grey_24dp_img.pngic_warning_white_24dp_img.pngreader_background_checkbox_selected.pngreader_background_checkbox_unselected.png
drawable-ldpi
drawable-mdpi
application_logo_144dp.pngic_clear_grey_24dp_img.pngic_refresh_grey_24dp_img.pngic_refresh_white_24dp_img.pngic_system_update_grey_24dp_img.pngic_warning_white_24dp_img.pngreader_background_checkbox_selected.pngreader_background_checkbox_unselected.png
drawable-nodpi
ic_manga_updates.webpic_tracker_anilist.webpic_tracker_bangumi.webpic_tracker_kavita.webpic_tracker_kitsu.webpic_tracker_komga.webpic_tracker_mal.webpic_tracker_shikimori.webpic_tracker_suwayomi.webpupdates_grid_widget_preview.webp
drawable-v21
drawable-v26
drawable-xhdpi
application_logo_144dp.pngcard_background.9.pngic_clear_grey_24dp_img.pngic_refresh_grey_24dp_img.pngic_refresh_white_24dp_img.pngic_system_update_grey_24dp_img.pngic_warning_white_24dp_img.png
drawable-xxhdpi
application_logo_144dp.pngic_clear_grey_24dp_img.pngic_refresh_grey_24dp_img.pngic_refresh_white_24dp_img.pngic_system_update_grey_24dp_img.pngic_warning_white_24dp_img.pngreader_background_checkbox_selected.pngreader_background_checkbox_unselected.png
drawable-xxxhdpi
application_logo_144dp.pngic_clear_grey_24dp_img.pngic_refresh_grey_24dp_img.pngic_refresh_white_24dp_img.pngic_system_update_grey_24dp_img.pngic_warning_white_24dp_img.pngreader_background_checkbox_selected.pngreader_background_checkbox_unselected.png
drawable
anim_browse_enter.xmlanim_caret_down.xmlanim_history_enter.xmlanim_library_enter.xmlanim_more_enter.xmlanim_updates_enter.xmlbranded_logo.xmlcover_error.xmlempty_drawable_32dp.xmlgradient_shape.xmlic_add_white_24dp.xmlic_arrow_down_white_32dp.xmlic_arrow_up_white_32dp.xmlic_backup_black_24dp.xmlic_blank_24dp.xmlic_book_24dp.xmlic_book_black_128dp.xmlic_book_black_24dp.xmlic_bookmark_24dp.xmlic_bookmark_border_white_24dp.xmlic_bookmark_white_24dp.xmlic_brightness_5_24dp.xmlic_check_24dp.xmlic_check_box_24dp.xmlic_check_box_outline_blank_24dp.xmlic_check_box_x_24dp.xmlic_clear_black_24dp.xmlic_create_white_24dp.xmlic_crop_24dp.xmlic_crop_off_24dp.xmlic_crop_original_white_24dp.xmlic_delete_24dp.xmlic_delete_white_24dp.xmlic_discord_24dp.xmlic_done_all_grey_24dp.xmlic_done_all_white_24dp.xmlic_done_green_24dp.xmlic_done_prev_24dp.xmlic_download_chapter_24dp.xmlic_drag_handle_24dp.xmlic_expand_less_24dp.xmlic_expand_less_white_36dp.xmlic_expand_more_24dp.xmlic_expand_more_white_36dp.xmlic_explore_black_24dp.xmlic_explore_blue_24dp.xmlic_extension_24dp.xmlic_facebook_24dp.xmlic_file_download_black_128dp.xmlic_file_download_black_24dp.xmlic_file_download_white_24dp.xmlic_filter_list_white_24dp.xmlic_folder_24dp.xmlic_github_24dp.xmlic_glasses_24dp.xmlic_history_black_128dp.xmlic_history_black_24dp.xmlic_info_24dp.xmlic_label_white_24dp.xmlic_launcher_foreground.xmlic_menu_white_24dp.xmlic_more_horiz_black_24dp.xmlic_more_vert_white_24dp.xmlic_offline_pin_24dp.xmlic_pause_24dp.xmlic_pause_white_24dp.xmlic_photo_24dp.xmlic_play_arrow_24dp.xmlic_play_arrow_white_24dp.xmlic_play_arrow_white_36dp.xmlic_reader_continuous_vertical_24dp.xmlic_reader_default_24dp.xmlic_reader_ltr_24dp.xmlic_reader_rtl_24dp.xmlic_reader_vertical_24dp.xmlic_reader_webtoon_24dp.xmlic_reddit_24dp.xmlic_refresh_white_24dp.xmlic_reorder_grey_24dp.xmlic_save_24dp.xmlic_screen_lock_landscape_white_24dp.xmlic_screen_lock_portrait_white_24dp.xmlic_screen_rotation_white_24dp.xmlic_search_white_24dp.xmlic_select_all_white_24dp.xmlic_settings_24dp.xmlic_settings_black_24dp.xmlic_share_24dp.xmlic_skip_next_24dp.xmlic_skip_next_white_24dp.xmlic_skip_previous_24dp.xmlic_skip_previous_white_24dp.xmlic_sort_by_alpha_white_24dp.xmlic_stay_current_landscape_24dp.xmlic_stay_current_portrait_24dp.xmlic_system_update_alt_white_24dp.xmlic_tachi.xmlic_tachi_monochrome_launcher.xmlic_tachi_splash.xmlic_twitter_24dp.xmlic_view_carousel_white_24dp.xmlic_view_list_white_24dp.xmlic_view_module_white_24dp.xmlic_warning_white_24dp.xmlic_webview_24dp.xmlic_zoom_out_map_white_24dp.xmlicon.pngline_divider_dark.xmlline_divider_light.xmllist_item_selector.xmllist_item_selector_dark.xmllist_item_selector_light.xmlmaterial_popup_background.xmlmaterial_thumb_drawable.xmlreader_background_checkbox.xmlsc_collections_bookmark_48dp.xmlsc_explore_48dp.xmlsc_history_48dp.xmlsc_new_releases_48dp.xmltransparent_tabs_background.xml
layout
activity_edit_categories.xmlactivity_main.xmlactivity_manga.xmlactivity_preferences.xmlactivity_reader.xmlcard_myanimelist_personal.xmlchapter_image.xmlcommon_spinner_item.xmlcommon_tabbed_sheet.xmlcompose_controller.xmldialog_myanimelist_chapters.xmldialog_myanimelist_score.xmldialog_myanimelist_search.xmldialog_myanimelist_search_item.xmldownload_header.xmldownload_item.xmldownload_list.xmlfragment_backup.xmlfragment_catalogue.xmlfragment_download_queue.xmlfragment_library.xmlfragment_library_category.xmlfragment_manga_chapters.xmlfragment_manga_info.xmlfragment_myanimelist.xmlfragment_recent_chapters.xmlitem_catalogue_grid.xmlitem_catalogue_list.xmlitem_chapter.xmlitem_download.xmlitem_edit_categories.xmlitem_pager_reader.xmlitem_recent_chapter.xmlitem_recent_chapter_section.xmlitem_webtoon_reader.xmllistitem_dir.xmlmaterial_fastscroll.xmlnavigation_header.xmlnavigation_view_checkbox.xmlnavigation_view_checkedtext.xmlnavigation_view_group.xmlnavigation_view_radio.xmlnavigation_view_spinner.xmlnavigation_view_text.xmlpref_account_login.xmlpref_library_columns.xmlpref_spinner.xmlpref_widget_switch_material.xmlpreference_widget_imageview.xmlreader_activity.xmlreader_color_filter_settings.xmlreader_error.xmlreader_general_settings.xmlreader_menu.xmlreader_page_sheet.xmlreader_pager_settings.xmlreader_popup.xmlreader_reading_mode_settings.xmlreader_transition_view.xmlreader_webtoon_settings.xmlsource_filter_sheet.xmlsource_preferences_controller.xmltoolbar.xml
menu
catalogue_list.xmlcategory_selection.xmlchapter_recent.xmlchapter_selection.xmlchapter_single.xmlchapters.xmldownload_queue.xmldownload_single.xmllibrary.xmllibrary_selection.xmlmenu_manga_detail.xmlmenu_navigation.xmlreader.xml
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
values-land
values-night-v31
values-night
bools.xmlcolor_lavender.xmlcolors.xmlcolors_greenapple.xmlcolors_midnightdusk.xmlcolors_strawberry.xmlcolors_tachiyomi.xmlcolors_tako.xmlcolors_tealturqoise.xmlcolors_tidalwave.xmlcolors_yinyang.xmlcolors_yotsuba.xmlthemes.xml
values-sw600dp
values-sw720dp
values-v21
values-v27
values-v28
values-v31
values-w820dp
values
arrays.xmlattrs.xmlbools.xmlcolor_lavender.xmlcolors.xmldimens.xmlids.xmlkeys.xmlstrings.xmlstyles.xmlthemes.xml
xml
standard
test
java
buildSrc
core
.gitignorebuild.gradle.kts
src
main
AndroidManifest.xml
java
eu
kanade
tachiyomi
core
security
network
AndroidCookieJar.ktDohProviders.ktJavaScriptEngine.ktNetworkHelper.ktNetworkPreferences.ktOkHttpExtensions.ktRequests.kt
interceptor
util
tachiyomi
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
.gitignorebuild.gradle.kts
src
main
AndroidManifest.xml
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-ml
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-ur
values-uz
values-vi
values-zh-rCN
values-zh-rTW
values
macrobenchmark
presentation-core
presentation-widget
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
settings.gradlesettings.gradle.ktssrc
main
source-api
@ -1,7 +0,0 @@
|
|||||||
[*.{kt,kts}]
|
|
||||||
indent_size=4
|
|
||||||
insert_final_newline=true
|
|
||||||
ij_kotlin_allow_trailing_comma=true
|
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
|
24
.gitattributes
vendored
24
.gitattributes
vendored
@ -1,24 +0,0 @@
|
|||||||
* text=auto
|
|
||||||
* text eol=lf
|
|
||||||
|
|
||||||
# Windows forced line-endings
|
|
||||||
/.idea/* text eol=crlf
|
|
||||||
|
|
||||||
# Gradle wrapper
|
|
||||||
*.jar binary
|
|
||||||
|
|
||||||
# Images
|
|
||||||
*.webp binary
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.ico binary
|
|
||||||
*.gz binary
|
|
||||||
*.zip binary
|
|
||||||
*.7z binary
|
|
||||||
*.ttf binary
|
|
||||||
*.eot binary
|
|
||||||
*.woff binary
|
|
||||||
*.pyc binary
|
|
||||||
*.swp binary
|
|
30
.github/CONTRIBUTING.md
vendored
Normal file
30
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Bugs
|
||||||
|
* Include version (Setting > About > Version)
|
||||||
|
* If not latest, try updating, it may have already been solved
|
||||||
|
* Debug version is equal to the number of commits as seen in the main page
|
||||||
|
* Include steps to reproduce (if not obvious from description)
|
||||||
|
* Include screenshot (if needed)
|
||||||
|
* If it could be device-dependent, try reproducing on another device (if possible), include results and device names, OS, modifications (root, Xposed)
|
||||||
|
* **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||||
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
|
* For multipart issues use list like this:
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
* [x] Done
|
||||||
|
* [ ] Not done
|
||||||
|
```
|
||||||
|
|
||||||
|
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
||||||
|
|
||||||
|
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
||||||
|
|
||||||
|
# Feature requests
|
||||||
|
|
||||||
|
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
||||||
|
* Include screenshot (if needed)
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
File `app/src/main/res/values/strings.xml` should be copied over to appropriate directories and then translated.
|
||||||
|
Consult [Android.com](http://developer.android.com/training/basics/supporting-devices/languages.html#CreateDirs)
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
ko_fi: inorichi
|
|
35
.github/ISSUE_TEMPLATE.md
vendored
35
.github/ISSUE_TEMPLATE.md
vendored
@ -1,34 +1,7 @@
|
|||||||
**PLEASE READ THIS**
|
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
||||||
|
|
||||||
I acknowledge that:
|
Remove line above and describe your issue here. Fill out version below. Use Preview.
|
||||||
|
|
||||||
- I have updated:
|
|
||||||
- To the latest version of the app (stable is v0.14.6)
|
|
||||||
- All extensions
|
|
||||||
- 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
|
|
||||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
|
||||||
- I will fill out the title and the information in this template
|
|
||||||
|
|
||||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
Version: r000 or v0.0.0
|
||||||
|
(other relevant info like OS)
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Device information
|
|
||||||
* Tachiyomi version: ?
|
|
||||||
* Android version: ?
|
|
||||||
* Device: ?
|
|
||||||
|
|
||||||
## Steps to reproduce
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
|
|
||||||
## Issue/Request
|
|
||||||
?
|
|
||||||
|
|
||||||
## Other details
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
|
|
||||||
|
11
.github/ISSUE_TEMPLATE/config.yml
vendored
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: ⚠️ Extension/source issue
|
|
||||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
|
||||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
|
||||||
- name: 📦 Tachiyomi extensions
|
|
||||||
url: https://tachiyomi.org/extensions
|
|
||||||
about: List of all available extensions with download links
|
|
||||||
- name: 🖥️ Tachiyomi website
|
|
||||||
url: https://tachiyomi.org/help/
|
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
|
106
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
106
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -1,106 +0,0 @@
|
|||||||
name: 🐞 Issue report
|
|
||||||
description: Report an issue in Tachiyomi
|
|
||||||
labels: [Bug]
|
|
||||||
body:
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduce-steps
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: Provide an example of the issue.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
1. First step
|
|
||||||
2. Second step
|
|
||||||
3. Issue here
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: expected-behavior
|
|
||||||
attributes:
|
|
||||||
label: Expected behavior
|
|
||||||
description: Explain what you should expect to happen.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This should happen..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: actual-behavior
|
|
||||||
attributes:
|
|
||||||
label: Actual behavior
|
|
||||||
description: Explain what actually happens.
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"This happened instead..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: crash-logs
|
|
||||||
attributes:
|
|
||||||
label: Crash logs
|
|
||||||
description: |
|
|
||||||
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
|
||||||
placeholder: |
|
|
||||||
You can paste the crash logs in plain text or upload it as an attachment.
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: tachiyomi-version
|
|
||||||
attributes:
|
|
||||||
label: Tachiyomi version
|
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
|
||||||
placeholder: |
|
|
||||||
Example: "0.14.6"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: android-version
|
|
||||||
attributes:
|
|
||||||
label: Android version
|
|
||||||
description: You can find this somewhere in your Android settings.
|
|
||||||
placeholder: |
|
|
||||||
Example: "Android 11"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: device
|
|
||||||
attributes:
|
|
||||||
label: Device
|
|
||||||
description: List your device and model.
|
|
||||||
placeholder: |
|
|
||||||
Example: "Google Pixel 5"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
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).
|
|
||||||
required: true
|
|
||||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
|
||||||
required: true
|
|
||||||
- label: I have updated all installed extensions.
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
39
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
39
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: ⭐ Feature request
|
|
||||||
description: Suggest a feature to improve Tachiyomi
|
|
||||||
labels: [Feature request]
|
|
||||||
body:
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: feature-description
|
|
||||||
attributes:
|
|
||||||
label: Describe your suggested feature
|
|
||||||
description: How can Tachiyomi be improved?
|
|
||||||
placeholder: |
|
|
||||||
Example:
|
|
||||||
"It should work like this..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: other-details
|
|
||||||
attributes:
|
|
||||||
label: Other details
|
|
||||||
placeholder: |
|
|
||||||
Additional details and attachments.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
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).
|
|
||||||
required: true
|
|
||||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
|
||||||
required: true
|
|
||||||
- label: I will fill out all of the requested information in this form.
|
|
||||||
required: true
|
|
10
.github/mergify.yml
vendored
10
.github/mergify.yml
vendored
@ -1,10 +0,0 @@
|
|||||||
#pull_request_rules:
|
|
||||||
# - name: Automatically merge translations
|
|
||||||
# conditions:
|
|
||||||
# - "author = weblate"
|
|
||||||
# - "-conflict"
|
|
||||||
# - "current-day-of-week = Sat"
|
|
||||||
# - "created-at < 1 day ago"
|
|
||||||
# actions:
|
|
||||||
# merge:
|
|
||||||
# method: squash
|
|
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@ -1,12 +0,0 @@
|
|||||||
<!--
|
|
||||||
Please include a summary of the change and which issue is fixed.
|
|
||||||
Also make sure you've tested your code and also done a self-review of it.
|
|
||||||
Don't forget to check all base themes and tablet mode for relevant changes.
|
|
||||||
|
|
||||||
If your changes are visual, please provide images below:
|
|
||||||
|
|
||||||
### Images
|
|
||||||
| Image 1 | Image 2 |
|
|
||||||
| ------- | ------- |
|
|
||||||
|  |  |
|
|
||||||
-->
|
|
BIN
.github/readme-images/app-icon.png
vendored
BIN
.github/readme-images/app-icon.png
vendored
Binary file not shown.
Before ![]() (image error) Size: 1.1 KiB |
12
.github/renovate.json
vendored
12
.github/renovate.json
vendored
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
],
|
|
||||||
"schedule": ["every sunday"],
|
|
||||||
"ignoreDeps": [
|
|
||||||
"androidx.core:core-splashscreen",
|
|
||||||
"com.android.tools:r8",
|
|
||||||
"com.google.guava:guava",
|
|
||||||
"com.github.commandiron:WheelPickerCompose"
|
|
||||||
]
|
|
||||||
}
|
|
39
.github/workflows/build_pull_request.yml
vendored
39
.github/workflows/build_pull_request.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: PR build check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- 'i18n/src/main/res/**/strings.xml'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build app
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/wrapper-validation-action@v1
|
|
||||||
|
|
||||||
- name: Dependency Review
|
|
||||||
uses: actions/dependency-review-action@v3
|
|
||||||
|
|
||||||
- name: Set up JDK 11
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
distribution: adopt
|
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
|
||||||
uses: gradle/gradle-command-action@v2
|
|
||||||
with:
|
|
||||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
|
106
.github/workflows/build_push.yml
vendored
106
.github/workflows/build_push.yml
vendored
@ -1,106 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build app
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/wrapper-validation-action@v1
|
|
||||||
|
|
||||||
- name: Set up JDK 11
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
distribution: adopt
|
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
|
||||||
uses: gradle/gradle-command-action@v2
|
|
||||||
with:
|
|
||||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
|
||||||
|
|
||||||
- name: Get tag name
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Sign APK
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
uses: r0adkll/sign-android-release@v1
|
|
||||||
with:
|
|
||||||
releaseDirectory: app/build/outputs/apk/standard/release
|
|
||||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
|
||||||
alias: ${{ secrets.ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
|
||||||
sha=`sha256sum tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.VERSION_TAG }}
|
|
||||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
|
||||||
body: |
|
|
||||||
---
|
|
||||||
|
|
||||||
### Checksums
|
|
||||||
|
|
||||||
| Variant | SHA-256 |
|
|
||||||
| ------- | ------- |
|
|
||||||
| Universal | ${{ env.APK_UNIVERSAL_SHA }}
|
|
||||||
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }}
|
|
||||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
|
||||||
| x86 | ${{ env.APK_X86_SHA }} |
|
|
||||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
|
||||||
files: |
|
|
||||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
|
||||||
tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
.github/workflows/issue_moderator.yml
vendored
35
.github/workflows/issue_moderator.yml
vendored
@ -1,35 +0,0 @@
|
|||||||
name: Issue moderator
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
moderate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Moderate issues
|
|
||||||
uses: tachiyomiorg/issue-moderator-action@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
auto-close-rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
|
||||||
"message": "The acknowledgment section was not removed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
|
|
||||||
"message": "Requested information in the template was not filled out."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "both",
|
|
||||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
|
||||||
"ignoreCase": true,
|
|
||||||
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
|
|
||||||
}
|
|
||||||
]
|
|
19
.github/workflows/lock.yml
vendored
19
.github/workflows/lock.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
name: Lock threads
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Daily
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
# Manual trigger
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@v4
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
issue-inactive-days: '2'
|
|
||||||
pr-inactive-days: '2'
|
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -2,15 +2,8 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/build
|
||||||
.idea/
|
.idea/
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
|
*/build
|
||||||
# Built files
|
|
||||||
*/build
|
|
||||||
/build
|
|
||||||
*.apk
|
|
||||||
app/**/output.json
|
|
||||||
|
|
||||||
# Unnecessary file
|
|
||||||
*.swp
|
|
28
.travis.yml
Normal file
28
.travis.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
language: android
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- platform-tools
|
||||||
|
- tools
|
||||||
|
|
||||||
|
# The BuildTools version used by your project
|
||||||
|
- build-tools-23.0.3
|
||||||
|
- android-23
|
||||||
|
- extra-android-m2repository
|
||||||
|
- extra-google-m2repository
|
||||||
|
- extra-android-support
|
||||||
|
- extra-google-google_play_services
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- chmod +x gradlew
|
||||||
|
#Build, and run tests
|
||||||
|
script: "./gradlew clean assembleDebug testDebugUnitTest"
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle/caches/
|
||||||
|
- $HOME/.gradle/wrapper/
|
||||||
|
env:
|
||||||
|
- GRADLE_OPTS="-XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m"
|
@ -1,126 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community moderators are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community moderators have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community moderators responsible for enforcement at
|
|
||||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community moderators are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community moderators will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community moderators, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
|
||||||
version 2.1, available at
|
|
||||||
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
|
|
||||||
at [translations](https://www.contributor-covenant.org/translations).
|
|
@ -1,50 +0,0 @@
|
|||||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Thanks for your interest in contributing to Tachiyomi!
|
|
||||||
|
|
||||||
|
|
||||||
# Code contributions
|
|
||||||
|
|
||||||
Pull requests are welcome!
|
|
||||||
|
|
||||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
|
||||||
You do not need to ask for permission nor an assignment.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you.
|
|
||||||
|
|
||||||
- Basic [Android development](https://developer.android.com/)
|
|
||||||
- [Kotlin](https://kotlinlang.org/)
|
|
||||||
|
|
||||||
### Tools
|
|
||||||
|
|
||||||
- [Android Studio](https://developer.android.com/studio)
|
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
|
||||||
|
|
||||||
## Getting help
|
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/help/contribution/#translation) for more details.
|
|
||||||
|
|
||||||
|
|
||||||
# Forks
|
|
||||||
|
|
||||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
|
||||||
|
|
||||||
When creating a fork, remember to:
|
|
||||||
|
|
||||||
- To avoid confusion with the main app:
|
|
||||||
- Change the app name
|
|
||||||
- Change the app icon
|
|
||||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
|
||||||
- To avoid installation conflicts:
|
|
||||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
|
||||||
- To avoid having your data polluting the main app's analytics and crash report services:
|
|
||||||
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/standard/google-services.json) with your own
|
|
||||||
- If you want to use ACRA crash reporting, replace the `ACRA_URI` endpoint in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) with your own
|
|
26
LICENSE
26
LICENSE
@ -174,3 +174,29 @@
|
|||||||
of your accepting any such warranty or additional liability.
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
85
README.md
85
README.md
@ -1,77 +1,24 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
| Build | Download | Auto Update |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|----------|-------------|
|
||||||
|  | [](https://github.com/tachiyomiorg/tachiyomi/releases) | [](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) |
|
||||||
|
|
||||||
|
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
# Tachiyomi
|
**Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.**
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
|
||||||
|
|
||||||
## Features
|
Tachiyomi is a free and open source manga reader for Android.
|
||||||
|
|
||||||
Features include:
|
Keep in mind it's still a beta, so expect it to crash sometimes.
|
||||||
* Online reading from a variety of sources
|
|
||||||
* Local reading of downloaded content
|
# Features
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
|
||||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
* Online and offline reading
|
||||||
|
* Configurable reader with multiple viewers and settings
|
||||||
|
* MyAnimeList support
|
||||||
|
* Resume from the next unread chapter
|
||||||
|
* Chapter filtering
|
||||||
|
* Schedule searching for updates
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
|
||||||
* Schedule updating your library for new chapters
|
|
||||||
* Create backups locally to read offline or to your desired cloud service
|
|
||||||
|
|
||||||
## Download
|
|
||||||
Get the app from our [releases page](https://github.com/tachiyomiorg/tachiyomi/releases).
|
|
||||||
|
|
||||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/tachiyomi-preview/releases).
|
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
|
||||||
|
|
||||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
|
||||||
|
|
||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/tachiyomiorg/tachiyomi/releases) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Bugs</summary>
|
|
||||||
|
|
||||||
* Include version (More → About → Version)
|
|
||||||
* If not latest, try updating, it may have already been solved
|
|
||||||
* Preview version is equal to the number of commits as seen in the main page
|
|
||||||
* Include steps to reproduce (if not obvious from description)
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
|
||||||
* Don't group unrelated requests into one issue
|
|
||||||
|
|
||||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
|
||||||
|
|
||||||
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Feature Requests</summary>
|
|
||||||
|
|
||||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
|
|
||||||
Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Contributing</summary>
|
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>Code of Conduct</summary>
|
|
||||||
|
|
||||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
|
||||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
2
app/.gitignore
vendored
2
app/.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
.idea
|
195
app/build.gradle
Normal file
195
app/build.gradle
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
// Git is needed in your system PATH for these commands to work.
|
||||||
|
// If it's not installed, you can return a random value as a workaround
|
||||||
|
getCommitCount = {
|
||||||
|
return 'git rev-list --count origin/master'.execute().text.trim()
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
getGitSha = {
|
||||||
|
return 'git rev-parse --short HEAD'.execute().text.trim()
|
||||||
|
// return "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildTime = {
|
||||||
|
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||||
|
df.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||||
|
return df.format(new Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def includeUpdater() {
|
||||||
|
return hasProperty("include_updater")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 23
|
||||||
|
buildToolsVersion "23.0.3"
|
||||||
|
publishNonDefault true
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "eu.kanade.tachiyomi"
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 23
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
versionCode 7
|
||||||
|
versionName "0.2.1"
|
||||||
|
|
||||||
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
|
buildConfigField "boolean", "INCLUDE_UPDATER", "${includeUpdater()}"
|
||||||
|
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
versionNameSuffix ".${getCommitCount()}"
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
|
exclude 'LICENSE.txt'
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/LICENSE.txt'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
checkReleaseBuilds false
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/32759529/androidhttpclient-not-found-when-running-robolectric
|
||||||
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
generateStubs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
final SUPPORT_LIBRARY_VERSION = '23.3.0'
|
||||||
|
final DAGGER_VERSION = '2.2'
|
||||||
|
final OKHTTP_VERSION = '3.2.0'
|
||||||
|
final RETROFIT_VERSION = '2.0.1'
|
||||||
|
final STORIO_VERSION = '1.8.0'
|
||||||
|
final MOCKITO_VERSION = '1.10.19'
|
||||||
|
|
||||||
|
// Modified dependencies
|
||||||
|
compile 'com.github.inorichi:subsampling-scale-image-view:421fb81'
|
||||||
|
compile 'com.github.inorichi:ReactiveNetwork:69092ed'
|
||||||
|
|
||||||
|
// Android support library
|
||||||
|
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:preference-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
compile "com.android.support:preference-v14:$SUPPORT_LIBRARY_VERSION"
|
||||||
|
|
||||||
|
// ReactiveX
|
||||||
|
compile 'io.reactivex:rxandroid:1.1.0'
|
||||||
|
compile 'io.reactivex:rxjava:1.1.1'
|
||||||
|
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
||||||
|
|
||||||
|
// Network client
|
||||||
|
compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"
|
||||||
|
compile "com.squareup.okhttp3:okhttp-urlconnection:$OKHTTP_VERSION"
|
||||||
|
|
||||||
|
// REST
|
||||||
|
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
|
||||||
|
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
|
||||||
|
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
|
||||||
|
|
||||||
|
// IO
|
||||||
|
compile 'com.squareup.okio:okio:1.7.0'
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
compile 'com.google.code.gson:gson:2.6.2'
|
||||||
|
|
||||||
|
// Disk cache
|
||||||
|
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||||
|
|
||||||
|
// Parse HTML
|
||||||
|
compile 'org.jsoup:jsoup:1.8.3'
|
||||||
|
|
||||||
|
// Database
|
||||||
|
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
||||||
|
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
||||||
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
|
|
||||||
|
// Model View Presenter
|
||||||
|
compile 'info.android15.nucleus:nucleus:3.0.0-beta'
|
||||||
|
|
||||||
|
// Dependency injection
|
||||||
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
provided 'org.glassfish:javax.annotation:10.0-b28'
|
||||||
|
|
||||||
|
// Image library
|
||||||
|
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
compile 'com.jakewharton.timber:timber:4.1.2'
|
||||||
|
|
||||||
|
// Crash reports
|
||||||
|
compile 'ch.acra:acra:4.8.5'
|
||||||
|
|
||||||
|
// UI
|
||||||
|
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
|
compile 'eu.davidea:flexible-adapter:4.2.0'
|
||||||
|
compile 'com.nononsenseapps:filepicker:2.5.2'
|
||||||
|
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
||||||
|
compile('com.github.afollestad.material-dialogs:core:0.8.5.5@aar') {
|
||||||
|
transitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||||
|
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
|
||||||
|
testCompile('org.robolectric:robolectric:3.0') {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||||
|
}
|
||||||
|
|
||||||
|
kaptTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.0.1'
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
@ -1,323 +0,0 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
|
||||||
kotlin("android")
|
|
||||||
kotlin("plugin.serialization")
|
|
||||||
id("com.github.zellius.shortcut-helper")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
|
||||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
|
||||||
|
|
||||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "eu.kanade.tachiyomi"
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
|
||||||
versionCode = 101
|
|
||||||
versionName = "0.14.6"
|
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
|
||||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
|
||||||
buildConfigField("boolean", "PREVIEW", "false")
|
|
||||||
|
|
||||||
// Please disable ACRA or use your own instance in forked versions of the project
|
|
||||||
buildConfigField("String", "ACRA_URI", "\"https://tachiyomi.kanade.eu/crash_report\"")
|
|
||||||
|
|
||||||
ndk {
|
|
||||||
abiFilters += SUPPORTED_ABIS
|
|
||||||
}
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
splits {
|
|
||||||
abi {
|
|
||||||
isEnable = true
|
|
||||||
reset()
|
|
||||||
include(*SUPPORTED_ABIS.toTypedArray())
|
|
||||||
isUniversalApk = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
named("debug") {
|
|
||||||
versionNameSuffix = "-${getCommitCount()}"
|
|
||||||
applicationIdSuffix = ".debug"
|
|
||||||
isPseudoLocalesEnabled = true
|
|
||||||
}
|
|
||||||
named("release") {
|
|
||||||
isShrinkResources = true
|
|
||||||
isMinifyEnabled = true
|
|
||||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
|
||||||
}
|
|
||||||
create("preview") {
|
|
||||||
initWith(getByName("release"))
|
|
||||||
buildConfigField("boolean", "PREVIEW", "true")
|
|
||||||
|
|
||||||
val debugType = getByName("debug")
|
|
||||||
signingConfig = debugType.signingConfig
|
|
||||||
versionNameSuffix = debugType.versionNameSuffix
|
|
||||||
applicationIdSuffix = debugType.applicationIdSuffix
|
|
||||||
matchingFallbacks.add("release")
|
|
||||||
}
|
|
||||||
create("benchmark") {
|
|
||||||
initWith(getByName("release"))
|
|
||||||
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
matchingFallbacks.add("release")
|
|
||||||
isDebuggable = false
|
|
||||||
versionNameSuffix = "-benchmark"
|
|
||||||
applicationIdSuffix = ".benchmark"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
getByName("preview").res.srcDirs("src/debug/res")
|
|
||||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
|
||||||
}
|
|
||||||
|
|
||||||
flavorDimensions.add("default")
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
create("standard") {
|
|
||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "true")
|
|
||||||
dimension = "default"
|
|
||||||
}
|
|
||||||
create("dev") {
|
|
||||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
|
||||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
|
||||||
dimension = "default"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
|
||||||
resources.excludes.addAll(listOf(
|
|
||||||
"META-INF/DEPENDENCIES",
|
|
||||||
"LICENSE.txt",
|
|
||||||
"META-INF/LICENSE",
|
|
||||||
"META-INF/LICENSE.txt",
|
|
||||||
"META-INF/README.md",
|
|
||||||
"META-INF/NOTICE",
|
|
||||||
"META-INF/*.kotlin_module",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
dependenciesInfo {
|
|
||||||
includeInApk = false
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding = true
|
|
||||||
compose = true
|
|
||||||
|
|
||||||
// Disable some unused things
|
|
||||||
aidl = false
|
|
||||||
renderScript = false
|
|
||||||
shaders = false
|
|
||||||
}
|
|
||||||
|
|
||||||
lint {
|
|
||||||
abortOnError = false
|
|
||||||
checkReleaseBuilds = false
|
|
||||||
}
|
|
||||||
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":i18n"))
|
|
||||||
implementation(project(":core"))
|
|
||||||
implementation(project(":source-api"))
|
|
||||||
implementation(project(":data"))
|
|
||||||
implementation(project(":domain"))
|
|
||||||
implementation(project(":presentation-core"))
|
|
||||||
implementation(project(":presentation-widget"))
|
|
||||||
|
|
||||||
// Compose
|
|
||||||
implementation(platform(compose.bom))
|
|
||||||
implementation(compose.activity)
|
|
||||||
implementation(compose.foundation)
|
|
||||||
implementation(compose.material3.core)
|
|
||||||
implementation(compose.material.core)
|
|
||||||
implementation(compose.material.icons)
|
|
||||||
implementation(compose.animation)
|
|
||||||
implementation(compose.animation.graphics)
|
|
||||||
implementation(compose.ui.tooling)
|
|
||||||
implementation(compose.ui.util)
|
|
||||||
implementation(compose.accompanist.webview)
|
|
||||||
implementation(compose.accompanist.flowlayout)
|
|
||||||
implementation(compose.accompanist.permissions)
|
|
||||||
implementation(compose.accompanist.themeadapter)
|
|
||||||
implementation(compose.accompanist.systemuicontroller)
|
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
|
||||||
implementation(androidx.paging.compose)
|
|
||||||
|
|
||||||
implementation(libs.bundles.sqlite)
|
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
|
||||||
|
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
|
||||||
implementation(kotlinx.bundles.coroutines)
|
|
||||||
|
|
||||||
// AndroidX libraries
|
|
||||||
implementation(androidx.annotation)
|
|
||||||
implementation(androidx.appcompat)
|
|
||||||
implementation(androidx.biometricktx)
|
|
||||||
implementation(androidx.constraintlayout)
|
|
||||||
implementation(androidx.coordinatorlayout)
|
|
||||||
implementation(androidx.corektx)
|
|
||||||
implementation(androidx.splashscreen)
|
|
||||||
implementation(androidx.recyclerview)
|
|
||||||
implementation(androidx.viewpager)
|
|
||||||
implementation(androidx.profileinstaller)
|
|
||||||
|
|
||||||
implementation(androidx.bundles.lifecycle)
|
|
||||||
|
|
||||||
// Job scheduling
|
|
||||||
implementation(androidx.bundles.workmanager)
|
|
||||||
|
|
||||||
// RX
|
|
||||||
implementation(libs.bundles.reactivex)
|
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
|
||||||
// Network client
|
|
||||||
implementation(libs.bundles.okhttp)
|
|
||||||
implementation(libs.okio)
|
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
|
||||||
implementation(libs.conscrypt.android)
|
|
||||||
|
|
||||||
// Data serialization (JSON, protobuf)
|
|
||||||
implementation(kotlinx.bundles.serialization)
|
|
||||||
|
|
||||||
// HTML parser
|
|
||||||
implementation(libs.jsoup)
|
|
||||||
|
|
||||||
// Disk
|
|
||||||
implementation(libs.disklrucache)
|
|
||||||
implementation(libs.unifile)
|
|
||||||
implementation(libs.junrar)
|
|
||||||
|
|
||||||
// Preferences
|
|
||||||
implementation(libs.preferencektx)
|
|
||||||
|
|
||||||
// Dependency injection
|
|
||||||
implementation(libs.injekt.core)
|
|
||||||
|
|
||||||
// Image loading
|
|
||||||
implementation(libs.bundles.coil)
|
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
|
||||||
exclude(module = "image-decoder")
|
|
||||||
}
|
|
||||||
implementation(libs.image.decoder)
|
|
||||||
|
|
||||||
// Sort
|
|
||||||
implementation(libs.natural.comparator)
|
|
||||||
|
|
||||||
// UI libraries
|
|
||||||
implementation(libs.material)
|
|
||||||
implementation(libs.flexible.adapter.core)
|
|
||||||
implementation(libs.flexible.adapter.ui)
|
|
||||||
implementation(libs.photoview)
|
|
||||||
implementation(libs.directionalviewpager) {
|
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
|
||||||
}
|
|
||||||
implementation(libs.insetter)
|
|
||||||
implementation(libs.bundles.richtext)
|
|
||||||
implementation(libs.aboutLibraries.compose)
|
|
||||||
implementation(libs.cascade)
|
|
||||||
implementation(libs.bundles.voyager)
|
|
||||||
implementation(libs.wheelpicker)
|
|
||||||
implementation(libs.materialmotion.core)
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
implementation(libs.logcat)
|
|
||||||
|
|
||||||
// Crash reports/analytics
|
|
||||||
implementation(libs.acra.http)
|
|
||||||
"standardImplementation"(libs.firebase.analytics)
|
|
||||||
|
|
||||||
// Shizuku
|
|
||||||
implementation(libs.bundles.shizuku)
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
testImplementation(libs.junit)
|
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
|
||||||
// debugImplementation(libs.leakcanary.android)
|
|
||||||
implementation(libs.leakcanary.plumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
androidComponents {
|
|
||||||
beforeVariants { variantBuilder ->
|
|
||||||
// Disables standardBenchmark
|
|
||||||
if (variantBuilder.buildType == "benchmark") {
|
|
||||||
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 {
|
|
||||||
|
|
||||||
withType<LintTask>().configureEach {
|
|
||||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
|
||||||
withType<KotlinCompile> {
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
|
|
||||||
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath(kotlinx.gradle)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
-dontusemixedcaseclassnames
|
|
||||||
-verbose
|
|
||||||
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclassmembers enum * {
|
|
||||||
public static **[] values();
|
|
||||||
public static ** valueOf(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclassmembers class * implements android.os.Parcelable {
|
|
||||||
public static final ** CREATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class androidx.annotation.Keep
|
|
||||||
|
|
||||||
-keep @androidx.annotation.Keep class * {*;}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <fields>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembers class * {
|
|
||||||
@androidx.annotation.Keep <init>(...);
|
|
||||||
}
|
|
138
app/proguard-rules.pro
vendored
138
app/proguard-rules.pro
vendored
@ -1,26 +1,30 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
# Keep common dependencies used in extensions
|
-keep class eu.kanade.tachiyomi.injection.** { *; }
|
||||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
|
||||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
|
||||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
|
||||||
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
|
||||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
|
||||||
-keep,allowoptimization class okio.** { public protected *; }
|
|
||||||
-keep,allowoptimization class rx.** { public protected *; }
|
|
||||||
-keep,allowoptimization class org.jsoup.** { public protected *; }
|
|
||||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
|
||||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
|
||||||
|
|
||||||
# From extensions-lib
|
# OkHttp
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
-keepattributes Signature
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.SpecificHostRateLimitInterceptorKt { public protected *; }
|
-keepattributes *Annotation*
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.NetworkHelper { public protected *; }
|
-keep class okhttp3.** { *; }
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.OkHttpExtensionsKt { public protected *; }
|
-keep interface okhttp3.** { *; }
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
-dontwarn okhttp3.**
|
||||||
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
-dontwarn okio.**
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
# Okio
|
||||||
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-dontwarn java.nio.file.*
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Glide specific rules #
|
||||||
|
# https://github.com/bumptech/glide
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||||
@ -36,34 +40,82 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
-dontnote rx.internal.util.PlatformDependent
|
# Retrofit 2.X
|
||||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
## https://square.github.io/retrofit/ ##
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
-dontwarn retrofit2.**
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keep class retrofit2.** { *; }
|
||||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
-keepattributes Signature
|
||||||
|
-keepattributes Exceptions
|
||||||
|
|
||||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
-keepclasseswithmembers class * {
|
||||||
-keepclassmembers class kotlinx.serialization.json.** {
|
@retrofit2.http.* <methods>;
|
||||||
*** Companion;
|
|
||||||
}
|
|
||||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep,includedescriptorclasses class eu.kanade.**$$serializer { *; }
|
# AppCombat
|
||||||
-keepclassmembers class eu.kanade.** {
|
-keep public class android.support.v7.widget.** { *; }
|
||||||
*** Companion;
|
-keep public class android.support.v7.internal.widget.** { *; }
|
||||||
}
|
-keep public class android.support.v7.internal.view.menu.** { *; }
|
||||||
-keepclasseswithmembers class eu.kanade.** {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
-keep public class * extends android.support.v4.view.ActionProvider {
|
||||||
|
public <init>(android.content.Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class kotlinx.serialization.**
|
## GSON 2.2.4 specific rules ##
|
||||||
-keepclassmembers class kotlinx.serialization.** {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
|
||||||
|
|
||||||
# XmlUtil
|
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
# removes such information by default, so configure it to keep all of it.
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
-keepattributes EnclosingMethod
|
||||||
|
|
||||||
|
# Gson specific classes
|
||||||
|
-keep class sun.misc.Unsafe { *; }
|
||||||
|
-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
|
## ACRA 4.5.0 specific rules ##
|
||||||
|
|
||||||
|
# we need line numbers in our stack traces otherwise they are pretty useless
|
||||||
|
-renamesourcefileattribute SourceFile
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# ACRA needs "annotations" so add this...
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
# keep this class so that logging will show 'ACRA' and not a obfuscated name like 'a'.
|
||||||
|
# Note: if you are removing log messages elsewhere in this file then this isn't necessary
|
||||||
|
-keep class org.acra.ACRA {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this around for some enums that ACRA needs
|
||||||
|
-keep class org.acra.ReportingInteractionMode {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.sender.HttpSender$** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class org.acra.ReportField {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void addCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void putCustomData(java.lang.String,java.lang.String);
|
||||||
|
public void removeCustomData(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep this otherwise it is removed by ProGuard
|
||||||
|
-keep public class org.acra.ErrorReporter {
|
||||||
|
public void handleSilentException(java.lang.Throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep the support library
|
||||||
|
-keep class org.acra.** { *; }
|
||||||
|
-keep interface org.acra.** { *; }
|
@ -1,47 +0,0 @@
|
|||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_library"
|
|
||||||
android:shortcutLongLabel="@string/label_library"
|
|
||||||
android:shortcutShortLabel="@string/label_library">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_new_releases_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_updated"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_updates"
|
|
||||||
android:shortcutShortLabel="@string/label_recent_updates">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_history_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_recently_read"
|
|
||||||
android:shortcutLongLabel="@string/label_recent_manga"
|
|
||||||
android:shortcutShortLabel="@string/label_recent_manga">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/sc_explore_48dp"
|
|
||||||
android:shortcutDisabledMessage="@string/app_not_available"
|
|
||||||
android:shortcutId="show_catalogues"
|
|
||||||
android:shortcutLongLabel="@string/browse"
|
|
||||||
android:shortcutShortLabel="@string/browse">
|
|
||||||
<intent
|
|
||||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
|
||||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
@ -1,27 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108.0"
|
|
||||||
android:viewportHeight="108.0">
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillColor="#455A64"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
|
||||||
android:fillColor="#607D8B"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#CE2828"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-19.94,0a19.94,19.94 0,1 1,39.87 0a19.94,19.94 0,1 1,-39.87 0"
|
|
||||||
android:fillColor="#FFF"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.04,46.3L47.42,46.3C46.14,46.3 44.93,46.23 44.2,46.14L44.2,49.76C45,49.65 46.16,49.6 47.42,49.6L60.58,49.6C61.86,49.6 63.02,49.65 63.82,49.76L63.82,46.14C63.09,46.23 61.86,46.3 60.58,46.3L55.69,46.3L55.69,45.07C55.69,44.43 55.73,43.95 55.82,43.45L51.9,43.45C51.99,44 52.04,44.43 52.04,45.07L52.04,46.3ZM46.78,60.68C45.46,60.68 44.29,60.63 43.45,60.52L43.45,64.14C44.34,64.03 45.46,63.98 46.78,63.98L61.29,63.98C62.57,63.98 63.71,64.03 64.57,64.14L64.57,60.52C63.73,60.63 62.57,60.68 61.29,60.68L58.24,60.68C59.33,58.06 59.99,56.23 60.7,53.91C61.34,51.81 61.34,51.81 61.56,51.13L57.58,50.06C57.51,50.93 57.37,51.52 56.89,53.41C56.19,56.14 55.32,58.74 54.5,60.68L46.78,60.68ZM46.48,51.36C47.55,54.02 48.28,56.53 49.03,60.15L52.66,58.9C51.65,54.98 50.92,52.66 49.94,50.11L46.48,51.36Z"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
</vector>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
|
||||||
</adaptive-icon>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@android:color/transparent"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
|
||||||
</adaptive-icon>
|
|
Binary file not shown.
Before ![]() (image error) Size: 2.6 KiB |
Binary file not shown.
Before ![]() (image error) Size: 4.1 KiB |
Binary file not shown.
Before ![]() (image error) Size: 1.5 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.9 KiB |
Binary file not shown.
Before ![]() (image error) Size: 5.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 5.5 KiB |
Binary file not shown.
Before ![]() (image error) Size: 10 KiB |
Binary file not shown.
Before ![]() (image error) Size: 6.4 KiB |
Binary file not shown.
Before ![]() (image error) Size: 13 KiB |
@ -1,263 +1,108 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
package="eu.kanade.tachiyomi">
|
||||||
|
|
||||||
<!-- Internet -->
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
|
|
||||||
<!-- Storage -->
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<!-- For background jobs -->
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
|
||||||
<!-- For managing extensions -->
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
|
||||||
<!-- To view extension packages in API 30+ -->
|
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
||||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
|
||||||
|
|
||||||
<!-- Remove permission from Firebase dependency -->
|
|
||||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
|
||||||
tools:node="remove" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:theme="@style/Theme.Tachiyomi" >
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
|
||||||
|
|
||||||
<!-- enable profiling by macrobenchmark -->
|
|
||||||
<profileable
|
|
||||||
android:shell="true"
|
|
||||||
tools:targetApi="q" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:theme="@style/Theme.BrandedLaunch">
|
||||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!--suppress AndroidDomInspection -->
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.shortcuts"
|
|
||||||
android:resource="@xml/shortcuts" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":error_handler"
|
android:name=".ui.manga.MangaActivity"
|
||||||
android:name=".crash.CrashActivity"
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.main.DeepLinkActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
|
||||||
android:label="@string/action_global_search"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.searchable"
|
|
||||||
android:resource="@xml/searchable" />
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:launchMode="singleTask"
|
android:theme="@style/Theme.Reader">
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
|
||||||
android:resource="@xml/s_pen_actions"/>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.security.UnlockActivity"
|
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.webview.WebViewActivity"
|
|
||||||
android:configChanges="uiMode|orientation|screenSize"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".extension.util.ExtensionInstallActivity"
|
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
|
||||||
android:label="Anilist"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="anilist-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
android:name=".ui.setting.SettingsActivity"
|
||||||
android:label="MyAnimeList"
|
android:label="@string/label_settings"
|
||||||
android:exported="true">
|
android:parentActivityName=".ui.main.MainActivity" >
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="myanimelist-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
android:name=".ui.category.CategoryActivity"
|
||||||
android:label="Shikimori"
|
android:label="@string/label_categories"
|
||||||
android:exported="true">
|
android:parentActivityName=".ui.main.MainActivity">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="shikimori-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
||||||
android:label="Bangumi"
|
android:label="@string/app_name"
|
||||||
android:exported="true">
|
android:theme="@style/FilePickerTheme">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="bangumi-auth"
|
|
||||||
android:scheme="tachiyomi" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service android:name=".data.library.LibraryUpdateService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<service android:name=".data.download.DownloadService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<service android:name=".data.mangasync.UpdateMangaSyncService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
|
||||||
android:enabled="@bool/glance_appwidget_available"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/label_recent_updates">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateService"
|
android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
|
||||||
android:exported="false" />
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.download.DownloadService"
|
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.updater.AppUpdateService"
|
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
|
||||||
android:exported="false" />
|
</receiver>
|
||||||
|
|
||||||
<service
|
<receiver
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.library.LibraryUpdateAlarm">
|
||||||
android:exported="false" />
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.UPDATE_LIBRARY" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".extension.util.ExtensionInstallService"
|
<receiver
|
||||||
android:exported="false" />
|
android:name=".data.updater.UpdateDownloaderAlarm">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.CHECK_UPDATE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
|
||||||
android:enabled="false"
|
|
||||||
android:exported="false">
|
|
||||||
<meta-data
|
|
||||||
android:name="autoStoreLocales"
|
|
||||||
android:value="true" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.provider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/provider_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
|
||||||
android:authorities="${applicationId}.shizuku"
|
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.webkit.WebView.EnableSafeBrowsing"
|
android:name="eu.kanade.tachiyomi.data.cache.CoverGlideModule"
|
||||||
android:value="false" />
|
android:value="GlideModule" />
|
||||||
<meta-data
|
|
||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
|
||||||
android:value="true" />
|
|
||||||
|
|
||||||
<!-- Disable advertising ID collection for Firebase -->
|
|
||||||
<meta-data
|
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
|
||||||
android:value="false" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
BIN
app/src/main/assets/fonts/PTSans-Narrow.ttf
Normal file
BIN
app/src/main/assets/fonts/PTSans-Narrow.ttf
Normal file
Binary file not shown.
BIN
app/src/main/assets/fonts/PTSans-NarrowBold.ttf
Normal file
BIN
app/src/main/assets/fonts/PTSans-NarrowBold.ttf
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before ![]() (image error) Size: 22 KiB After ![]() (image error) Size: 25 KiB ![]() ![]() |
@ -1,55 +0,0 @@
|
|||||||
package eu.kanade.core.prefs
|
|
||||||
|
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
|
|
||||||
sealed class CheckboxState<T>(open val value: T) {
|
|
||||||
abstract fun next(): CheckboxState<T>
|
|
||||||
|
|
||||||
sealed class State<T>(override val value: T) : CheckboxState<T>(value) {
|
|
||||||
data class Checked<T>(override val value: T) : State<T>(value)
|
|
||||||
data class None<T>(override val value: T) : State<T>(value)
|
|
||||||
|
|
||||||
val isChecked: Boolean
|
|
||||||
get() = this is Checked
|
|
||||||
|
|
||||||
override fun next(): CheckboxState<T> {
|
|
||||||
return when (this) {
|
|
||||||
is Checked -> None(value)
|
|
||||||
is None -> Checked(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sealed class TriState<T>(override val value: T) : CheckboxState<T>(value) {
|
|
||||||
data class Include<T>(override val value: T) : TriState<T>(value)
|
|
||||||
data class Exclude<T>(override val value: T) : TriState<T>(value)
|
|
||||||
data class None<T>(override val value: T) : TriState<T>(value)
|
|
||||||
|
|
||||||
override fun next(): CheckboxState<T> {
|
|
||||||
return when (this) {
|
|
||||||
is Exclude -> None(value)
|
|
||||||
is Include -> Exclude(value)
|
|
||||||
is None -> Include(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun asState(): ToggleableState {
|
|
||||||
return when (this) {
|
|
||||||
is Exclude -> ToggleableState.Indeterminate
|
|
||||||
is Include -> ToggleableState.On
|
|
||||||
is None -> ToggleableState.Off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> T.asCheckboxState(condition: (T) -> Boolean): CheckboxState.State<T> {
|
|
||||||
return if (condition(this)) {
|
|
||||||
CheckboxState.State.Checked(this)
|
|
||||||
} else {
|
|
||||||
CheckboxState.State.None(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> List<T>.mapAsCheckboxState(condition: (T) -> Boolean): List<CheckboxState.State<T>> {
|
|
||||||
return this.map { it.asCheckboxState(condition) }
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package eu.kanade.core.prefs
|
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import tachiyomi.core.preference.Preference
|
|
||||||
|
|
||||||
class PreferenceMutableState<T>(
|
|
||||||
private val preference: Preference<T>,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
) : MutableState<T> {
|
|
||||||
|
|
||||||
private val state = mutableStateOf(preference.get())
|
|
||||||
|
|
||||||
init {
|
|
||||||
preference.changes()
|
|
||||||
.onEach { state.value = it }
|
|
||||||
.launchIn(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override var value: T
|
|
||||||
get() = state.value
|
|
||||||
set(value) {
|
|
||||||
preference.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun component1(): T {
|
|
||||||
return state.value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun component2(): (T) -> Unit {
|
|
||||||
return { preference.set(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
|
@ -1,148 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
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,61 +0,0 @@
|
|||||||
package eu.kanade.core.util
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.CoroutineStart
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import rx.Emitter
|
|
||||||
import rx.Observable
|
|
||||||
import rx.Observer
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
|
||||||
val observer = object : Observer<T> {
|
|
||||||
override fun onNext(t: T) {
|
|
||||||
trySend(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
|
||||||
close(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompleted() {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val subscription = subscribe(observer)
|
|
||||||
awaitClose { subscription.unsubscribe() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> Flow<T>.asObservable(
|
|
||||||
context: CoroutineContext = Dispatchers.Unconfined,
|
|
||||||
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
|
|
||||||
): Observable<T> {
|
|
||||||
return Observable.create(
|
|
||||||
{ emitter ->
|
|
||||||
/*
|
|
||||||
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
|
|
||||||
* asObservable is already invoked from unconfined
|
|
||||||
*/
|
|
||||||
val job = GlobalScope.launch(context = context, start = CoroutineStart.ATOMIC) {
|
|
||||||
try {
|
|
||||||
collect { emitter.onNext(it) }
|
|
||||||
emitter.onCompleted()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
|
|
||||||
if (e !is CancellationException) {
|
|
||||||
emitter.onError(e)
|
|
||||||
} else {
|
|
||||||
emitter.onCompleted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emitter.setCancellation { job.cancel() }
|
|
||||||
},
|
|
||||||
backpressureMode,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
|
|
||||||
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
|
||||||
Source(
|
|
||||||
source.id,
|
|
||||||
source.lang,
|
|
||||||
source.name,
|
|
||||||
supportsLatest = false,
|
|
||||||
isStub = source is SourceManager.StubSource,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
|
||||||
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import androidx.paging.PagingState
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import tachiyomi.core.util.lang.awaitSingle
|
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
|
||||||
|
|
||||||
abstract class SourcePagingSource(
|
|
||||||
protected val source: CatalogueSource,
|
|
||||||
) : SourcePagingSourceType() {
|
|
||||||
|
|
||||||
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
|
||||||
|
|
||||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
|
|
||||||
val page = params.key ?: 1
|
|
||||||
|
|
||||||
val mangasPage = try {
|
|
||||||
withIOContext {
|
|
||||||
requestNextPage(page.toInt())
|
|
||||||
.takeIf { it.mangas.isNotEmpty() }
|
|
||||||
?: throw NoResultsException()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return LoadResult.Error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadResult.Page(
|
|
||||||
data = mangasPage.mangas,
|
|
||||||
prevKey = null,
|
|
||||||
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
|
|
||||||
return state.anchorPosition?.let { anchorPosition ->
|
|
||||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
|
||||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : SourcePagingSource(source) {
|
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
|
||||||
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
|
||||||
return source.fetchPopularManga(currentPage).awaitSingle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
|
||||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
|
||||||
return source.fetchLatestUpdates(currentPage).awaitSingle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoResultsException : Exception()
|
|
@ -1,74 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import tachiyomi.data.DatabaseHandler
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
|
||||||
|
|
||||||
class SourceRepositoryImpl(
|
|
||||||
private val sourceManager: SourceManager,
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : SourceRepository {
|
|
||||||
|
|
||||||
override fun getSources(): Flow<List<Source>> {
|
|
||||||
return sourceManager.catalogueSources.map { sources ->
|
|
||||||
sources.map(catalogueSourceMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOnlineSources(): Flow<List<Source>> {
|
|
||||||
return sourceManager.onlineSources.map { sources ->
|
|
||||||
sources.map(sourceMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
|
||||||
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
|
||||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
|
||||||
sourceIdsWithCount
|
|
||||||
.filterNot { it.source == LocalSource.ID }
|
|
||||||
.map { (sourceId, count) ->
|
|
||||||
val source = sourceManager.getOrStub(sourceId).run {
|
|
||||||
sourceMapper(this)
|
|
||||||
}
|
|
||||||
source to count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
|
|
||||||
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
|
||||||
return sourceIdWithNonLibraryManga.map { sourceId ->
|
|
||||||
sourceId.map { (sourceId, count) ->
|
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
|
||||||
SourceWithCount(sourceMapper(source), count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun search(
|
|
||||||
sourceId: Long,
|
|
||||||
query: String,
|
|
||||||
filterList: FilterList,
|
|
||||||
): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourceSearchPagingSource(source, query, filterList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPopular(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourcePopularPagingSource(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLatest(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourceLatestPagingSource(source)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package eu.kanade.domain
|
|
||||||
|
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
|
||||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
|
||||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
|
||||||
import eu.kanade.domain.category.interactor.RenameCategory
|
|
||||||
import eu.kanade.domain.category.interactor.ReorderCategory
|
|
||||||
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
|
||||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
|
||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
|
||||||
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
|
||||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
|
||||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
|
||||||
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
|
||||||
import eu.kanade.domain.manga.interactor.GetLibraryManga
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
|
||||||
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
|
|
||||||
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
|
|
||||||
import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
|
||||||
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
|
||||||
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.InjektRegistrar
|
|
||||||
import uy.kohesive.injekt.api.addFactory
|
|
||||||
import uy.kohesive.injekt.api.addSingletonFactory
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class DomainModule : InjektModule {
|
|
||||||
|
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
|
||||||
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
|
||||||
addFactory { GetCategories(get()) }
|
|
||||||
addFactory { ResetCategoryFlags(get(), get()) }
|
|
||||||
addFactory { SetDisplayModeForCategory(get(), get()) }
|
|
||||||
addFactory { SetSortModeForCategory(get(), get()) }
|
|
||||||
addFactory { CreateCategoryWithName(get(), get()) }
|
|
||||||
addFactory { RenameCategory(get()) }
|
|
||||||
addFactory { ReorderCategory(get()) }
|
|
||||||
addFactory { UpdateCategory(get()) }
|
|
||||||
addFactory { DeleteCategory(get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
|
||||||
addFactory { GetDuplicateLibraryManga(get()) }
|
|
||||||
addFactory { GetFavorites(get()) }
|
|
||||||
addFactory { GetLibraryManga(get()) }
|
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
|
||||||
addFactory { GetManga(get()) }
|
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
|
||||||
addFactory { ResetViewerFlags(get()) }
|
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
|
||||||
addFactory { SetMangaViewerFlags(get()) }
|
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
|
||||||
addFactory { UpdateManga(get()) }
|
|
||||||
addFactory { SetMangaCategories(get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
|
||||||
addFactory { DeleteTrack(get()) }
|
|
||||||
addFactory { GetTracksPerManga(get()) }
|
|
||||||
addFactory { GetTracks(get()) }
|
|
||||||
addFactory { InsertTrack(get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
|
||||||
addFactory { GetChapter(get()) }
|
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
|
||||||
addFactory { UpdateChapter(get()) }
|
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
|
||||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
|
||||||
addFactory { GetHistory(get()) }
|
|
||||||
addFactory { UpsertHistory(get()) }
|
|
||||||
addFactory { RemoveHistory(get()) }
|
|
||||||
addFactory { GetTotalReadDuration(get()) }
|
|
||||||
|
|
||||||
addFactory { DeleteDownload(get(), get()) }
|
|
||||||
|
|
||||||
addFactory { GetExtensionsByType(get(), get()) }
|
|
||||||
addFactory { GetExtensionSources(get()) }
|
|
||||||
addFactory { GetExtensionLanguages(get(), get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
|
||||||
addFactory { GetUpdates(get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
|
||||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
|
||||||
addFactory { GetEnabledSources(get(), get()) }
|
|
||||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
|
||||||
addFactory { GetRemoteManga(get()) }
|
|
||||||
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
|
||||||
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
|
||||||
addFactory { SetMigrateSorting(get()) }
|
|
||||||
addFactory { ToggleLanguage(get()) }
|
|
||||||
addFactory { ToggleSource(get()) }
|
|
||||||
addFactory { ToggleSourcePin(get()) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.domain.backup.service
|
|
||||||
|
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
|
||||||
import tachiyomi.core.provider.FolderProvider
|
|
||||||
|
|
||||||
class BackupPreferences(
|
|
||||||
private val folderProvider: FolderProvider,
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun backupsDirectory() = preferenceStore.getString("backup_directory", folderProvider.path())
|
|
||||||
|
|
||||||
fun numberOfBackups() = preferenceStore.getInt("backup_slots", 2)
|
|
||||||
|
|
||||||
fun backupInterval() = preferenceStore.getInt("backup_interval", 12)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.domain.base
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
|
||||||
|
|
||||||
class BasePreferences(
|
|
||||||
val context: Context,
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", false)
|
|
||||||
|
|
||||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
|
||||||
|
|
||||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
|
||||||
|
|
||||||
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
|
|
||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
|
||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
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,46 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
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(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val initialFlags: Long
|
|
||||||
get() {
|
|
||||||
val sort = preferences.librarySortingMode().get()
|
|
||||||
return preferences.libraryDisplayMode().get().flag or
|
|
||||||
sort.type.flag or
|
|
||||||
sort.direction.flag
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(name: String): Result = withNonCancellableContext {
|
|
||||||
val categories = categoryRepository.getAll()
|
|
||||||
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
|
||||||
val newCategory = Category(
|
|
||||||
id = 0,
|
|
||||||
name = name,
|
|
||||||
order = nextOrder,
|
|
||||||
flags = initialFlags,
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
|
||||||
categoryRepository.insert(newCategory)
|
|
||||||
Result.Success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
Result.InternalError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
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(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long) = withNonCancellableContext {
|
|
||||||
try {
|
|
||||||
categoryRepository.delete(categoryId)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
return@withNonCancellableContext Result.InternalError(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
val categories = categoryRepository.getAll()
|
|
||||||
val updates = categories.mapIndexed { index, category ->
|
|
||||||
CategoryUpdate(
|
|
||||||
id = category.id,
|
|
||||||
order = index.toLong(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
categoryRepository.updatePartial(updates)
|
|
||||||
Result.Success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
Result.InternalError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
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(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
|
||||||
val update = CategoryUpdate(
|
|
||||||
id = categoryId,
|
|
||||||
name = name,
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
|
||||||
categoryRepository.updatePartial(update)
|
|
||||||
Result.Success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
Result.InternalError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(category: Category, name: String) = await(category.id, name)
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
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(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
suspend fun moveUp(category: Category): Result =
|
|
||||||
await(category, MoveTo.UP)
|
|
||||||
|
|
||||||
suspend fun moveDown(category: Category): Result =
|
|
||||||
await(category, MoveTo.DOWN)
|
|
||||||
|
|
||||||
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
|
|
||||||
mutex.withLock {
|
|
||||||
val categories = categoryRepository.getAll()
|
|
||||||
.filterNot(Category::isSystemCategory)
|
|
||||||
.toMutableList()
|
|
||||||
|
|
||||||
val currentIndex = categories.indexOfFirst { it.id == category.id }
|
|
||||||
if (currentIndex == -1) {
|
|
||||||
return@withNonCancellableContext Result.Unchanged
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
object Unchanged : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum class MoveTo {
|
|
||||||
UP,
|
|
||||||
DOWN,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
|
||||||
import tachiyomi.domain.library.model.plus
|
|
||||||
|
|
||||||
class ResetCategoryFlags(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await() {
|
|
||||||
val display = preferences.libraryDisplayMode().get()
|
|
||||||
val sort = preferences.librarySortingMode().get()
|
|
||||||
categoryRepository.updateAllFlags(display + sort.type + sort.direction)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
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(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, display: LibraryDisplayMode) {
|
|
||||||
val category = categoryRepository.get(categoryId) ?: return
|
|
||||||
val flags = category.flags + display
|
|
||||||
if (preferences.categorizedDisplaySettings().get()) {
|
|
||||||
categoryRepository.updatePartial(
|
|
||||||
CategoryUpdate(
|
|
||||||
id = category.id,
|
|
||||||
flags = flags,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
preferences.libraryDisplayMode().set(display)
|
|
||||||
categoryRepository.updateAllFlags(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(category: Category, display: LibraryDisplayMode) {
|
|
||||||
await(category.id, display)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import logcat.LogPriority
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class SetMangaCategories(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
|
|
||||||
try {
|
|
||||||
mangaRepository.setMangaCategories(mangaId, categoryIds)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
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(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
|
||||||
val category = categoryRepository.get(categoryId) ?: return
|
|
||||||
val flags = category.flags + type + direction
|
|
||||||
if (preferences.categorizedDisplaySettings().get()) {
|
|
||||||
categoryRepository.updatePartial(
|
|
||||||
CategoryUpdate(
|
|
||||||
id = category.id,
|
|
||||||
flags = flags,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
preferences.librarySortingMode().set(LibrarySort(type, direction))
|
|
||||||
categoryRepository.updateAllFlags(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(category: Category, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
|
||||||
await(category.id, type, direction)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
|
||||||
import tachiyomi.domain.category.model.CategoryUpdate
|
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
|
||||||
|
|
||||||
class UpdateCategory(
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(payload: CategoryUpdate): Result = withNonCancellableContext {
|
|
||||||
try {
|
|
||||||
categoryRepository.updatePartial(payload)
|
|
||||||
Result.Success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.Error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
data class Error(val error: Exception) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import logcat.LogPriority
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
|
||||||
|
|
||||||
class GetChapter(
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(id: Long): Chapter? {
|
|
||||||
return try {
|
|
||||||
chapterRepository.getChapterById(id)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(url: String, mangaId: Long): Chapter? {
|
|
||||||
return try {
|
|
||||||
chapterRepository.getChapterByUrlAndMangaId(url, mangaId)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import logcat.LogPriority
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
|
||||||
|
|
||||||
class GetChapterByMangaId(
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long): List<Chapter> {
|
|
||||||
return try {
|
|
||||||
chapterRepository.getChapterByMangaId(mangaId)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
|
|
||||||
class SetMangaDefaultChapterFlags(
|
|
||||||
private val libraryPreferences: LibraryPreferences,
|
|
||||||
private val setMangaChapterFlags: SetMangaChapterFlags,
|
|
||||||
private val getFavorites: GetFavorites,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(manga: Manga) {
|
|
||||||
withNonCancellableContext {
|
|
||||||
with(libraryPreferences) {
|
|
||||||
setMangaChapterFlags.awaitSetAllFlags(
|
|
||||||
mangaId = manga.id,
|
|
||||||
unreadFilter = filterChapterByRead().get(),
|
|
||||||
downloadedFilter = filterChapterByDownloaded().get(),
|
|
||||||
bookmarkedFilter = filterChapterByBookmarked().get(),
|
|
||||||
sortingMode = sortChapterBySourceOrNumber().get(),
|
|
||||||
sortingDirection = sortChapterByAscendingOrDescending().get(),
|
|
||||||
displayMode = displayChapterByNameOrNumber().get(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitAll() {
|
|
||||||
withNonCancellableContext {
|
|
||||||
getFavorites.await().forEach { await(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
|
||||||
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(
|
|
||||||
private val downloadPreferences: DownloadPreferences,
|
|
||||||
private val deleteDownload: DeleteDownload,
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val mapper = { chapter: Chapter, read: Boolean ->
|
|
||||||
ChapterUpdate(
|
|
||||||
read = read,
|
|
||||||
lastPageRead = if (!read) 0 else null,
|
|
||||||
id = chapter.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
|
||||||
val chaptersToUpdate = chapters.filter {
|
|
||||||
when (read) {
|
|
||||||
true -> !it.read
|
|
||||||
false -> it.read || it.lastPageRead > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (chaptersToUpdate.isEmpty()) {
|
|
||||||
return@withNonCancellableContext Result.NoChapters
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
chapterRepository.updateAll(
|
|
||||||
chaptersToUpdate.map { mapper(it, read) },
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
return@withNonCancellableContext Result.InternalError(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
|
|
||||||
chaptersToUpdate
|
|
||||||
.groupBy { it.mangaId }
|
|
||||||
.forEach { (mangaId, chapters) ->
|
|
||||||
deleteDownload.awaitAll(
|
|
||||||
manga = mangaRepository.getMangaById(mangaId),
|
|
||||||
chapters = chapters.toTypedArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.Success
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, read: Boolean): Result = withNonCancellableContext {
|
|
||||||
await(
|
|
||||||
read = read,
|
|
||||||
chapters = chapterRepository
|
|
||||||
.getChapterByMangaId(mangaId)
|
|
||||||
.toTypedArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(manga: Manga, read: Boolean) =
|
|
||||||
await(manga.id, read)
|
|
||||||
|
|
||||||
sealed class Result {
|
|
||||||
object Success : Result()
|
|
||||||
object NoChapters : Result()
|
|
||||||
data class InternalError(val error: Throwable) : Result()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
|
||||||
import eu.kanade.domain.chapter.model.toSChapter
|
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.isLocal
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
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.api.get
|
|
||||||
import java.lang.Long.max
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.TreeSet
|
|
||||||
|
|
||||||
class SyncChaptersWithSource(
|
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
|
||||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
|
||||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
|
||||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to synchronize db chapters with source ones
|
|
||||||
*
|
|
||||||
* @param rawSourceChapters the chapters from the source.
|
|
||||||
* @param manga the manga the chapters belong to.
|
|
||||||
* @param source the source the manga belongs to.
|
|
||||||
* @return Newly added chapters
|
|
||||||
*/
|
|
||||||
suspend fun await(
|
|
||||||
rawSourceChapters: List<SChapter>,
|
|
||||||
manga: Manga,
|
|
||||||
source: Source,
|
|
||||||
): List<Chapter> {
|
|
||||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
|
||||||
throw NoChaptersException()
|
|
||||||
}
|
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
|
||||||
.distinctBy { it.url }
|
|
||||||
.mapIndexed { i, sChapter ->
|
|
||||||
Chapter.create()
|
|
||||||
.copyFromSChapter(sChapter)
|
|
||||||
.copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) })
|
|
||||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chapters from db.
|
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
|
||||||
val toAdd = mutableListOf<Chapter>()
|
|
||||||
|
|
||||||
// Chapters whose metadata have changed.
|
|
||||||
val toChange = mutableListOf<Chapter>()
|
|
||||||
|
|
||||||
// Chapters from the db not in source.
|
|
||||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
|
||||||
sourceChapters.any { sourceChapter ->
|
|
||||||
dbChapter.url == sourceChapter.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val rightNow = Date().time
|
|
||||||
|
|
||||||
// Used to not set upload date of older chapters
|
|
||||||
// to a higher value than newer chapters
|
|
||||||
var maxSeenUploadDate = 0L
|
|
||||||
|
|
||||||
val sManga = manga.toSManga()
|
|
||||||
for (sourceChapter in sourceChapters) {
|
|
||||||
var chapter = sourceChapter
|
|
||||||
|
|
||||||
// Update metadata from source if necessary.
|
|
||||||
if (source is HttpSource) {
|
|
||||||
val sChapter = chapter.toSChapter()
|
|
||||||
source.prepareNewChapter(sChapter, sManga)
|
|
||||||
chapter = chapter.copyFromSChapter(sChapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recognize chapter number for the chapter.
|
|
||||||
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
|
|
||||||
chapter = chapter.copy(chapterNumber = chapterNumber)
|
|
||||||
|
|
||||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
|
||||||
|
|
||||||
if (dbChapter == null) {
|
|
||||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
|
||||||
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
|
|
||||||
chapter.copy(dateUpload = altDateUpload)
|
|
||||||
} else {
|
|
||||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
toAdd.add(toAddChapter)
|
|
||||||
} else {
|
|
||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
|
||||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
|
||||||
}
|
|
||||||
var toChangeChapter = dbChapter.copy(
|
|
||||||
name = chapter.name,
|
|
||||||
chapterNumber = chapter.chapterNumber,
|
|
||||||
scanlator = chapter.scanlator,
|
|
||||||
sourceOrder = chapter.sourceOrder,
|
|
||||||
)
|
|
||||||
if (chapter.dateUpload != 0L) {
|
|
||||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
|
||||||
}
|
|
||||||
toChange.add(toChangeChapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
|
||||||
|
|
||||||
toDelete.forEach { chapter ->
|
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
|
||||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
|
||||||
deletedChapterNumbers.add(chapter.chapterNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
|
|
||||||
.associate { it.chapterNumber to it.dateFetch }
|
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
|
||||||
var itemCount = toAdd.size
|
|
||||||
var updatedToAdd = toAdd.map { toAddItem ->
|
|
||||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
|
||||||
|
|
||||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
|
||||||
|
|
||||||
chapter = chapter.copy(
|
|
||||||
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
|
||||||
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
|
|
||||||
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
|
||||||
chapter = chapter.copy(dateFetch = it)
|
|
||||||
}
|
|
||||||
|
|
||||||
reAdded.add(chapter)
|
|
||||||
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toDelete.isNotEmpty()) {
|
|
||||||
val toDeleteIds = toDelete.map { it.id }
|
|
||||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedToAdd.isNotEmpty()) {
|
|
||||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toChange.isNotEmpty()) {
|
|
||||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
|
||||||
// Note that last_update actually represents last time the chapter list changed at all
|
|
||||||
updateManga.awaitUpdateLastUpdate(manga.id)
|
|
||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
|
||||||
|
|
||||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
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.api.get
|
|
||||||
|
|
||||||
class SyncChaptersWithTrackServiceTwoWay(
|
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
|
||||||
private val insertTrack: InsertTrack = Injekt.get(),
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(
|
|
||||||
chapters: List<Chapter>,
|
|
||||||
remoteTrack: Track,
|
|
||||||
service: TrackService,
|
|
||||||
) {
|
|
||||||
val sortedChapters = chapters.sortedBy { it.chapterNumber }
|
|
||||||
val chapterUpdates = sortedChapters
|
|
||||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
|
||||||
.map { it.copy(read = true).toChapterUpdate() }
|
|
||||||
|
|
||||||
// only take into account continuous reading
|
|
||||||
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
|
||||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
|
||||||
|
|
||||||
try {
|
|
||||||
service.update(updatedTrack.toDbTrack())
|
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
|
||||||
insertTrack.await(updatedTrack)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logcat(LogPriority.WARN, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
|
||||||
|
|
||||||
import logcat.LogPriority
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
|
||||||
|
|
||||||
class UpdateChapter(
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(chapterUpdate: ChapterUpdate) {
|
|
||||||
try {
|
|
||||||
chapterRepository.update(chapterUpdate)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitAll(chapterUpdates: List<ChapterUpdate>) {
|
|
||||||
try {
|
|
||||||
chapterRepository.updateAll(chapterUpdates)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.domain.chapter.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
|
||||||
|
|
||||||
// TODO: Remove when all deps are migrated
|
|
||||||
fun Chapter.toSChapter(): SChapter {
|
|
||||||
return SChapter.create().also {
|
|
||||||
it.url = url
|
|
||||||
it.name = name
|
|
||||||
it.date_upload = dateUpload
|
|
||||||
it.chapter_number = chapterNumber
|
|
||||||
it.scanlator = scanlator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
it.id = id
|
|
||||||
it.manga_id = mangaId
|
|
||||||
it.url = url
|
|
||||||
it.name = name
|
|
||||||
it.scanlator = scanlator
|
|
||||||
it.read = read
|
|
||||||
it.bookmark = bookmark
|
|
||||||
it.last_page_read = lastPageRead.toInt()
|
|
||||||
it.date_fetch = dateFetch
|
|
||||||
it.date_upload = dateUpload
|
|
||||||
it.chapter_number = chapterNumber
|
|
||||||
it.source_order = sourceOrder.toInt()
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
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,19 +0,0 @@
|
|||||||
package eu.kanade.domain.download.interactor
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
|
|
||||||
class DeleteDownload(
|
|
||||||
private val sourceManager: SourceManager,
|
|
||||||
private val downloadManager: DownloadManager,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
|
||||||
sourceManager.get(manga.source)?.let { source ->
|
|
||||||
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.domain.download.service
|
|
||||||
|
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
|
||||||
import tachiyomi.core.provider.FolderProvider
|
|
||||||
|
|
||||||
class DownloadPreferences(
|
|
||||||
private val folderProvider: FolderProvider,
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun downloadsDirectory() = preferenceStore.getString("download_directory", folderProvider.path())
|
|
||||||
|
|
||||||
fun downloadOnlyOverWifi() = preferenceStore.getBoolean("pref_download_only_over_wifi_key", true)
|
|
||||||
|
|
||||||
fun saveChaptersAsCBZ() = preferenceStore.getBoolean("save_chapter_as_cbz", true)
|
|
||||||
|
|
||||||
fun splitTallImages() = preferenceStore.getBoolean("split_tall_images", false)
|
|
||||||
|
|
||||||
fun autoDownloadWhileReading() = preferenceStore.getInt("auto_download_while_reading", 0)
|
|
||||||
|
|
||||||
fun removeAfterReadSlots() = preferenceStore.getInt("remove_after_read_slots", -1)
|
|
||||||
|
|
||||||
fun removeAfterMarkedAsRead() = preferenceStore.getBoolean("pref_remove_after_marked_as_read_key", false)
|
|
||||||
|
|
||||||
fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false)
|
|
||||||
|
|
||||||
fun removeExcludeCategories() = preferenceStore.getStringSet("remove_exclude_categories", emptySet())
|
|
||||||
|
|
||||||
fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false)
|
|
||||||
|
|
||||||
fun downloadNewChapterCategories() = preferenceStore.getStringSet("download_new_categories", emptySet())
|
|
||||||
|
|
||||||
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
|
|
||||||
class GetExtensionLanguages(
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
private val extensionManager: ExtensionManager,
|
|
||||||
) {
|
|
||||||
fun subscribe(): Flow<List<String>> {
|
|
||||||
return combine(
|
|
||||||
preferences.enabledLanguages().changes(),
|
|
||||||
extensionManager.availableExtensionsFlow,
|
|
||||||
) { enabledLanguage, availableExtensions ->
|
|
||||||
availableExtensions
|
|
||||||
.flatMap { ext ->
|
|
||||||
if (ext.sources.isEmpty()) {
|
|
||||||
listOf(ext.lang)
|
|
||||||
} else {
|
|
||||||
ext.sources.map { it.lang }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.distinct()
|
|
||||||
.sortedWith(
|
|
||||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
class GetExtensionSources(
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(extension: Extension.Installed): Flow<List<ExtensionSourceItem>> {
|
|
||||||
val isMultiSource = extension.sources.size > 1
|
|
||||||
val isMultiLangSingleSource =
|
|
||||||
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
|
|
||||||
|
|
||||||
return preferences.disabledSources().changes().map { disabledSources ->
|
|
||||||
fun Source.isEnabled() = id.toString() !in disabledSources
|
|
||||||
|
|
||||||
extension.sources
|
|
||||||
.map { source ->
|
|
||||||
ExtensionSourceItem(
|
|
||||||
source = source,
|
|
||||||
enabled = source.isEnabled(),
|
|
||||||
labelAsName = isMultiSource && isMultiLangSingleSource.not(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ExtensionSourceItem(
|
|
||||||
val source: Source,
|
|
||||||
val enabled: Boolean,
|
|
||||||
val labelAsName: Boolean,
|
|
||||||
)
|
|
@ -1,60 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.extension.model.Extensions
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
|
|
||||||
class GetExtensionsByType(
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
private val extensionManager: ExtensionManager,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<Extensions> {
|
|
||||||
val showNsfwSources = preferences.showNsfwSource().get()
|
|
||||||
|
|
||||||
return combine(
|
|
||||||
preferences.enabledLanguages().changes(),
|
|
||||||
extensionManager.installedExtensionsFlow,
|
|
||||||
extensionManager.untrustedExtensionsFlow,
|
|
||||||
extensionManager.availableExtensionsFlow,
|
|
||||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
|
||||||
val (updates, installed) = _installed
|
|
||||||
.filter { (showNsfwSources || it.isNsfw.not()) }
|
|
||||||
.sortedWith(
|
|
||||||
compareBy<Extension.Installed> { it.isObsolete.not() }
|
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
|
||||||
)
|
|
||||||
.partition { it.hasUpdate }
|
|
||||||
|
|
||||||
val untrusted = _untrusted
|
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
|
||||||
|
|
||||||
val available = _available
|
|
||||||
.filter { extension ->
|
|
||||||
_installed.none { it.pkgName == extension.pkgName } &&
|
|
||||||
_untrusted.none { it.pkgName == extension.pkgName } &&
|
|
||||||
(showNsfwSources || extension.isNsfw.not())
|
|
||||||
}
|
|
||||||
.flatMap { ext ->
|
|
||||||
if (ext.sources.isEmpty()) {
|
|
||||||
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
|
||||||
}
|
|
||||||
ext.sources.filter { it.lang in _activeLanguages }
|
|
||||||
.map {
|
|
||||||
ext.copy(
|
|
||||||
name = it.name,
|
|
||||||
lang = it.lang,
|
|
||||||
pkgName = "${ext.pkgName}-${it.id}",
|
|
||||||
sources = listOf(it),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
|
||||||
|
|
||||||
Extensions(updates, installed, available, untrusted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
|
|
||||||
data class Extensions(
|
|
||||||
val updates: List<Extension.Installed>,
|
|
||||||
val installed: List<Extension.Installed>,
|
|
||||||
val available: List<Extension.Available>,
|
|
||||||
val untrusted: List<Extension.Untrusted>,
|
|
||||||
)
|
|
@ -1,52 +0,0 @@
|
|||||||
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,111 +0,0 @@
|
|||||||
package eu.kanade.domain.library.service
|
|
||||||
|
|
||||||
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_NON_COMPLETED
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
|
||||||
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(
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
|
||||||
|
|
||||||
fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
|
|
||||||
|
|
||||||
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
|
|
||||||
|
|
||||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
|
||||||
|
|
||||||
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
|
||||||
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
|
||||||
|
|
||||||
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
|
||||||
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
|
|
||||||
|
|
||||||
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
|
||||||
|
|
||||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
|
||||||
|
|
||||||
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
|
|
||||||
|
|
||||||
// region Filter
|
|
||||||
|
|
||||||
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Badges
|
|
||||||
|
|
||||||
fun downloadBadge() = preferenceStore.getBoolean("display_download_badge", false)
|
|
||||||
|
|
||||||
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
|
|
||||||
|
|
||||||
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
|
||||||
|
|
||||||
fun newShowUpdatesCount() = preferenceStore.getBoolean("library_show_updates_count", true)
|
|
||||||
fun newUpdatesCount() = preferenceStore.getInt("library_unseen_updates_count", 0)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Category
|
|
||||||
|
|
||||||
fun defaultCategory() = preferenceStore.getInt("default_category", -1)
|
|
||||||
|
|
||||||
fun lastUsedCategory() = preferenceStore.getInt("last_used_category", 0)
|
|
||||||
|
|
||||||
fun categoryTabs() = preferenceStore.getBoolean("display_category_tabs", true)
|
|
||||||
|
|
||||||
fun categoryNumberOfItems() = preferenceStore.getBoolean("display_number_of_items", false)
|
|
||||||
|
|
||||||
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
|
|
||||||
|
|
||||||
fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
|
|
||||||
|
|
||||||
fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Chapter
|
|
||||||
|
|
||||||
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
// and upload date
|
|
||||||
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
|
|
||||||
|
|
||||||
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
|
|
||||||
|
|
||||||
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
|
|
||||||
|
|
||||||
fun setChapterSettingsDefault(manga: Manga) {
|
|
||||||
filterChapterByRead().set(manga.unreadFilterRaw)
|
|
||||||
filterChapterByDownloaded().set(manga.downloadedFilterRaw)
|
|
||||||
filterChapterByBookmarked().set(manga.bookmarkedFilterRaw)
|
|
||||||
sortChapterBySourceOrNumber().set(manga.sorting)
|
|
||||||
displayChapterByNameOrNumber().set(manga.displayMode)
|
|
||||||
sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autoClearChapterCache() = preferenceStore.getBoolean("auto_clear_chapter_cache", false)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class GetDuplicateLibraryManga(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(title: String): Manga? {
|
|
||||||
return mangaRepository.getDuplicateLibraryManga(title.lowercase())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class GetFavorites(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): List<Manga> {
|
|
||||||
return mangaRepository.getFavorites()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(sourceId: Long): Flow<List<Manga>> {
|
|
||||||
return mangaRepository.getFavoritesBySourceId(sourceId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class GetLibraryManga(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): List<LibraryManga> {
|
|
||||||
return mangaRepository.getLibraryManga()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(): Flow<List<LibraryManga>> {
|
|
||||||
return mangaRepository.getLibraryMangaAsFlow()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import logcat.LogPriority
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class GetManga(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(id: Long): Manga? {
|
|
||||||
return try {
|
|
||||||
mangaRepository.getMangaById(id)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun subscribe(id: Long): Flow<Manga> {
|
|
||||||
return mangaRepository.getMangaByIdAsFlow(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
|
|
||||||
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
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(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
private val chapterRepository: ChapterRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
|
|
||||||
return combine(
|
|
||||||
mangaRepository.getMangaByIdAsFlow(id),
|
|
||||||
chapterRepository.getChapterByMangaIdAsFlow(id),
|
|
||||||
) { manga, chapters ->
|
|
||||||
Pair(manga, chapters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitManga(id: Long): Manga {
|
|
||||||
return mangaRepository.getMangaById(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitChapters(id: Long): List<Chapter> {
|
|
||||||
return chapterRepository.getChapterByMangaId(id)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class NetworkToLocalManga(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(manga: Manga): Manga {
|
|
||||||
val localManga = getManga(manga.url, manga.source)
|
|
||||||
return when {
|
|
||||||
localManga == null -> {
|
|
||||||
val id = insertManga(manga)
|
|
||||||
manga.copy(id = id!!)
|
|
||||||
}
|
|
||||||
!localManga.favorite -> {
|
|
||||||
// if the manga isn't a favorite, set its display title from source
|
|
||||||
// if it later becomes a favorite, updated title will go to db
|
|
||||||
localManga.copy(title = manga.title)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
localManga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getManga(url: String, sourceId: Long): Manga? {
|
|
||||||
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun insertManga(manga: Manga): Long? {
|
|
||||||
return mangaRepository.insert(manga)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class ResetViewerFlags(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): Boolean {
|
|
||||||
return mangaRepository.resetViewerFlags()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class SetMangaChapterFlags(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun awaitSetDownloadedFilter(manga: Manga, flag: Long): Boolean {
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = manga.id,
|
|
||||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_DOWNLOADED_MASK),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetUnreadFilter(manga: Manga, flag: Long): Boolean {
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = manga.id,
|
|
||||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_UNREAD_MASK),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetBookmarkFilter(manga: Manga, flag: Long): Boolean {
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = manga.id,
|
|
||||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_BOOKMARKED_MASK),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetDisplayMode(manga: Manga, flag: Long): Boolean {
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = manga.id,
|
|
||||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_DISPLAY_MASK),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetSortingModeOrFlipOrder(manga: Manga, flag: Long): Boolean {
|
|
||||||
val newFlags = manga.chapterFlags.let {
|
|
||||||
if (manga.sorting == flag) {
|
|
||||||
// Just flip the order
|
|
||||||
val orderFlag = if (manga.sortDescending()) {
|
|
||||||
Manga.CHAPTER_SORT_ASC
|
|
||||||
} else {
|
|
||||||
Manga.CHAPTER_SORT_DESC
|
|
||||||
}
|
|
||||||
it.setFlag(orderFlag, Manga.CHAPTER_SORT_DIR_MASK)
|
|
||||||
} else {
|
|
||||||
// Set new flag with ascending order
|
|
||||||
it
|
|
||||||
.setFlag(flag, Manga.CHAPTER_SORTING_MASK)
|
|
||||||
.setFlag(Manga.CHAPTER_SORT_ASC, Manga.CHAPTER_SORT_DIR_MASK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = manga.id,
|
|
||||||
chapterFlags = newFlags,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetAllFlags(
|
|
||||||
mangaId: Long,
|
|
||||||
unreadFilter: Long,
|
|
||||||
downloadedFilter: Long,
|
|
||||||
bookmarkedFilter: Long,
|
|
||||||
sortingMode: Long,
|
|
||||||
sortingDirection: Long,
|
|
||||||
displayMode: Long,
|
|
||||||
): Boolean {
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = mangaId,
|
|
||||||
chapterFlags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK)
|
|
||||||
.setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK)
|
|
||||||
.setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK)
|
|
||||||
.setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK)
|
|
||||||
.setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK)
|
|
||||||
.setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
|
||||||
return this and mask.inv() or (flag and mask)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
|
|
||||||
class SetMangaViewerFlags(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
|
||||||
val manga = mangaRepository.getMangaById(id)
|
|
||||||
mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = id,
|
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
|
||||||
val manga = mangaRepository.getMangaById(id)
|
|
||||||
mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = id,
|
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
|
||||||
return this and mask.inv() or (flag and mask)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
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.api.get
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class UpdateManga(
|
|
||||||
private val mangaRepository: MangaRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
|
||||||
return mangaRepository.update(mangaUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitAll(mangaUpdates: List<MangaUpdate>): Boolean {
|
|
||||||
return mangaRepository.updateAll(mangaUpdates)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitUpdateFromSource(
|
|
||||||
localManga: Manga,
|
|
||||||
remoteManga: SManga,
|
|
||||||
manualFetch: Boolean,
|
|
||||||
coverCache: CoverCache = Injekt.get(),
|
|
||||||
): Boolean {
|
|
||||||
val remoteTitle = try {
|
|
||||||
remoteManga.title
|
|
||||||
} catch (_: UninitializedPropertyAccessException) {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the manga isn't a favorite, set its title from source and update in db
|
|
||||||
val title = if (remoteTitle.isEmpty() || localManga.favorite) null else remoteTitle
|
|
||||||
|
|
||||||
val coverLastModified =
|
|
||||||
when {
|
|
||||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
|
||||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
|
||||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
|
||||||
localManga.isLocal() -> Date().time
|
|
||||||
localManga.hasCustomCover(coverCache) -> {
|
|
||||||
coverCache.deleteFromCache(localManga, false)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
coverCache.deleteFromCache(localManga, false)
|
|
||||||
Date().time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
|
||||||
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(
|
|
||||||
id = localManga.id,
|
|
||||||
title = title,
|
|
||||||
coverLastModified = coverLastModified,
|
|
||||||
author = remoteManga.author,
|
|
||||||
artist = remoteManga.artist,
|
|
||||||
description = remoteManga.description,
|
|
||||||
genre = remoteManga.getGenres(),
|
|
||||||
thumbnailUrl = thumbnailUrl,
|
|
||||||
status = remoteManga.status.toLong(),
|
|
||||||
updateStrategy = remoteManga.update_strategy,
|
|
||||||
initialized = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
|
||||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
|
||||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Date().time))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
|
||||||
val dateAdded = when (favorite) {
|
|
||||||
true -> Date().time
|
|
||||||
false -> 0
|
|
||||||
}
|
|
||||||
return mangaRepository.update(
|
|
||||||
MangaUpdate(id = mangaId, favorite = favorite, dateAdded = dateAdded),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlElement
|
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
|
||||||
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
|
|
||||||
@XmlSerialName("ComicInfo", "", "")
|
|
||||||
data class ComicInfo(
|
|
||||||
val title: Title?,
|
|
||||||
val series: Series?,
|
|
||||||
val summary: Summary?,
|
|
||||||
val writer: Writer?,
|
|
||||||
val penciller: Penciller?,
|
|
||||||
val inker: Inker?,
|
|
||||||
val colorist: Colorist?,
|
|
||||||
val letterer: Letterer?,
|
|
||||||
val coverArtist: CoverArtist?,
|
|
||||||
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"
|
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
|
||||||
@XmlElement(false)
|
|
||||||
@XmlSerialName("xmlns:xsi", "", "")
|
|
||||||
val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Title", "", "")
|
|
||||||
data class Title(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Series", "", "")
|
|
||||||
data class Series(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Summary", "", "")
|
|
||||||
data class Summary(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Writer", "", "")
|
|
||||||
data class Writer(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Penciller", "", "")
|
|
||||||
data class Penciller(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Inker", "", "")
|
|
||||||
data class Inker(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Colorist", "", "")
|
|
||||||
data class Colorist(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("Letterer", "", "")
|
|
||||||
data class Letterer(@XmlValue(true) val value: String = "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@XmlSerialName("CoverArtist", "", "")
|
|
||||||
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,93 +0,0 @@
|
|||||||
package eu.kanade.domain.manga.model
|
|
||||||
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
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.api.get
|
|
||||||
|
|
||||||
// TODO: move these into the domain model
|
|
||||||
val Manga.readingModeType: Long
|
|
||||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
|
||||||
|
|
||||||
val Manga.orientationType: Long
|
|
||||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
|
||||||
|
|
||||||
val Manga.downloadedFilter: TriStateFilter
|
|
||||||
get() {
|
|
||||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
|
||||||
return when (downloadedFilterRaw) {
|
|
||||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
|
||||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
|
||||||
else -> TriStateFilter.DISABLED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.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 Manga.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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SManga.toDomainManga(sourceId: Long): Manga {
|
|
||||||
return Manga.create().copy(
|
|
||||||
url = url,
|
|
||||||
title = title,
|
|
||||||
artist = artist,
|
|
||||||
author = author,
|
|
||||||
description = description,
|
|
||||||
genre = getGenres(),
|
|
||||||
status = status.toLong(),
|
|
||||||
thumbnailUrl = thumbnail_url,
|
|
||||||
updateStrategy = update_strategy,
|
|
||||||
initialized = initialized,
|
|
||||||
source = sourceId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
|
||||||
|
|
||||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
|
||||||
return coverCache.getCustomCoverFile(id).exists()
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
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(
|
|
||||||
private val repository: SourceRepository,
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Source>> {
|
|
||||||
return combine(
|
|
||||||
preferences.pinnedSources().changes(),
|
|
||||||
preferences.enabledLanguages().changes(),
|
|
||||||
preferences.disabledSources().changes(),
|
|
||||||
preferences.lastUsedSource().changes(),
|
|
||||||
repository.getSources(),
|
|
||||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
|
||||||
sources
|
|
||||||
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
|
||||||
.filterNot { it.id.toString() in disabledSources }
|
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
|
||||||
.flatMap {
|
|
||||||
val flag = if ("${it.id}" in pinnedSourceIds) Pins.pinned else Pins.unpinned
|
|
||||||
val source = it.copy(pin = flag)
|
|
||||||
val toFlatten = mutableListOf(source)
|
|
||||||
if (source.id == lastUsedSource) {
|
|
||||||
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
|
||||||
}
|
|
||||||
toFlatten
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
|
|
||||||
class GetLanguagesWithSources(
|
|
||||||
private val repository: SourceRepository,
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
|
||||||
return combine(
|
|
||||||
preferences.enabledLanguages().changes(),
|
|
||||||
preferences.disabledSources().changes(),
|
|
||||||
repository.getOnlineSources(),
|
|
||||||
) { enabledLanguage, disabledSource, onlineSources ->
|
|
||||||
val sortedSources = onlineSources.sortedWith(
|
|
||||||
compareBy<Source> { it.id.toString() in disabledSource }
|
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
|
||||||
)
|
|
||||||
|
|
||||||
sortedSources.groupBy { it.lang }
|
|
||||||
.toSortedMap(
|
|
||||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
|
|
||||||
class GetRemoteManga(
|
|
||||||
private val repository: SourceRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType {
|
|
||||||
return when (query) {
|
|
||||||
QUERY_POPULAR -> repository.getPopular(sourceId)
|
|
||||||
QUERY_LATEST -> repository.getLatest(sourceId)
|
|
||||||
else -> repository.search(sourceId, query, filterList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val QUERY_POPULAR = "eu.kanade.domain.source.interactor.POPULAR"
|
|
||||||
const val QUERY_LATEST = "eu.kanade.domain.source.interactor.LATEST"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
import java.text.Collator
|
|
||||||
import java.util.Collections
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class GetSourcesWithFavoriteCount(
|
|
||||||
private val repository: SourceRepository,
|
|
||||||
private val preferences: SourcePreferences,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
|
||||||
return combine(
|
|
||||||
preferences.migrationSortingDirection().changes(),
|
|
||||||
preferences.migrationSortingMode().changes(),
|
|
||||||
repository.getSourcesWithFavoriteCount(),
|
|
||||||
) { direction, mode, list ->
|
|
||||||
list.sortedWith(sortFn(direction, mode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortFn(
|
|
||||||
direction: SetMigrateSorting.Direction,
|
|
||||||
sorting: SetMigrateSorting.Mode,
|
|
||||||
): java.util.Comparator<Pair<Source, Long>> {
|
|
||||||
val locale = Locale.getDefault()
|
|
||||||
val collator = Collator.getInstance(locale).apply {
|
|
||||||
strength = Collator.PRIMARY
|
|
||||||
}
|
|
||||||
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
|
||||||
when (sorting) {
|
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
|
||||||
when {
|
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
|
||||||
b.first.isStub && a.first.isStub.not() -> 1
|
|
||||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetMigrateSorting.Mode.TOTAL -> {
|
|
||||||
when {
|
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
|
||||||
b.first.isStub && a.first.isStub.not() -> 1
|
|
||||||
else -> a.second.compareTo(b.second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (direction) {
|
|
||||||
SetMigrateSorting.Direction.ASCENDING -> Comparator(sortFn)
|
|
||||||
SetMigrateSorting.Direction.DESCENDING -> Collections.reverseOrder(sortFn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user