From 9b0e0fcfe65c174829e8d6f73cb338eec8eb156e Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Mon, 4 Oct 2021 17:30:10 -0400 Subject: [PATCH] Material 3 (#988) * use real material dialogs * Fixes for tristate preference * use material 1.5.0-alpha03 * Material 3 start * More Material 3 changes Updates to color primary for many themes Using material dialog in more places Instead of toolbar elevation, changing the color of the toolbar instead * fix toolbar bg coloring * More changes and fixes fixed dialog width when using forced side nav * update more color primaries * Fix toolbar title alpha * more updates Updates to many fonts (still more to come) Update to tracker dialogs to true material * More dialogs updated Using extensions for some old functionality in material dialog library * Removed final use of mat dialogs * Fully removed afollestad.material-dialogs you will be missed tbh * fix overflow width back to normal + bg color change for it * Fix RTL for reader slider * Fix RTL in general This way I can revert back the slider changes. thanks @arkon * Android 12/SDK 31 Target * restore ManageCategoryDialog's postive button action no longer dismisses if there's errors with something * Use disable items helper in MultiListMatPreference * Change statelist and ripple for update button in ext page * Update runtime-ktx and appcompat to alpha for A12 Target * Removing hyperion as its no longer compatible with A12 target Sorry Carlos, I never really never used it, even though its neat Maybe if the library gets an update, but it hasn't had once in Jan * Fix not being able to install extensions under A12 target * Fixed status bar color from going back to extensions list from ext settings * modernizing the action mode toolbar * tinting overlay background with a hint of secondary * Remove use of BuildCompat since `isAtLeastS` will be deprecated soon enough * Change startInstalling method in updateservice to only run on A12 * Revert dialog behavior for which category to update on swipe refresh * Adding primaryInverse used for snackbar action + reader progress when using a different background color from theme (i.e. yin theme with a white reader background) * More subtle coloring of the manga details backdrop theres still a tint but its next to unnoticeable now * Remove colorDownloadBadge for colorTertiary * Using more rounded indicator for tab chips * removed randomly extra line in styles * remove suggested_date_ string since no longer used * Update chucker version to 3.5.2 to support a12 target * No longer coloring the highlighted bottom nav text * many fixes to preference dialogs along with making the tri state pref match other choice pref dialogs * Switch tabs style to use M3 No real change as far as i can see * setting expedited for job as needed for bigger job in A12 * Changing active tab colors again changing to match other google apps (gmail, phone, photos) instead of trying to match other google apps (clock) Also upping the brightness of colorSecondaryVariant for many themes * Reverting back to target a11 Not worth trying to fix a12 issues with background jobs * More appropriate colors for colorSecondaryVariant in light themes * more updates to the active tab colors * Using sliders + switch material for the reader color filters Also updating the tints of switches Will fix #1004 * Changing library badge style + increasing size of grid items Library badge is now inside the grid, so less padding is needed for the grid item, thus its bigger now Since its bigger now, the search toolbar is now wider to match its width * Fix incognito badge not showing until title is showing on manga details * revert work runtime to 2.6.0 * fix library unread badge for list mode * fix text colors for download tooltips * Material3 popup in webview activity * removing more uses of MaterialComponents for M3 * update header card always show category name * fix missing text appearance * Cleanup SearchController * clean up SwitchPreferenceCategory * optimize imports of SearchController * Use SwitchPreferenceCompat in SettingsExtensionsController * Use of material 3 buttons in manga details library/tracking * Option to theme manga details page buttons based on the cover Backdrop tint color is now based on the covers dominant cover instead of the vibrant color, that is now used for the buttons Also prefetching those colors when loading the covers from other screens * removing stroke on the cover in details * improvements to the navigation icon + blue theme improvements + styling the swipe refresh to the magna theme * general cleanup/fixes when it comes to light status/nav bar also general fixes to webview theme switching * styles cleanup * improvements to the library grid selector * random refactoring thats honestly unrelated to this branch im nearing the end of development for m3 now * Details' bookmarks now themed by cover * Use Material3 for primary buttons * Use new mat dialog for filter scanlators sheet * Updates to themed details refresh icon * fix toolbar title being centered in searchactivity * Updates to library badges Now slightly outside of the covers again * Global search badge updated to match library badge * Updates to the manga details theme colors to be closer to the original suggested color Also the backdrop tint will no longer be used as much if it makes for a bad contrast between it and the title/subtitle text * Updates to the monet themes colorPrimaryVariant * more updates to cover theme colors tries to pick between dominant, vibrant, and muted to find something colorful to use * Updated to material alpha04 With it brings: M3 Nav Rail Updated dialogs Updated sheets theme * Delete side_nav_item_selector.xml * Update colorPrimVariant for light themes * remove elevation in theme preview item * various updates to imports * Update ControllerExtensions.kt * Fix extensions/ dl queue title not being centered * more improvements to bestColor for covers * Use attr for styles of textviews + style cleanup * Use cross in filter scanlator groups to show its filtered * Support delta movement of moving manga categories Tristate to manage add/remove without removing categories categories also some refactoring of TriStateCheckBox closes #644 * remove stroke on category hopper * update to details theming in light theme * Fix some text appearances * Fixes to text that shows when some categories are shared between selected manga * Use Material3 style for the final remaining elements this is slider tooltip, chips, text buttons, editText, tracker cardview * making category hopper slightly bigger * fixed dialog divider color for edit manga and tri state * fixed top padding in manga category dialog * Update dim for full cover for devices under A12 * More improvements to the logic for what text shows on the move categories button * Fixed slider tooltip color and text size as well as made the inactive ticks less visible * fixed light nav bar in bottom sheets for older devices * Fix reader nav bar colors for android 10 and below for android 8 and below the reader nav bar is now black in light themes also removed zhanghai.android.systemuihelper lib * set tri checkbox background ripple to off for dialogs * fixed misuse of textAppearanceBodySmall * make lightStatusBar a local val in readeractivity * when manga is in the default category, text should always say "move to" instead "add to" when change categories * Fixed copy and logic for when trying to remove tracker while offline --- app/build.gradle.kts | 26 +- app/src/main/AndroidManifest.xml | 22 +- .../tachiyomi/data/database/models/Manga.kt | 8 + .../data/download/DownloadNotifier.kt | 2 +- .../image/coil/LibraryMangaImageTarget.kt | 31 ++ .../data/library/LibraryUpdateNotifier.kt | 2 +- .../data/notification/NotificationHandler.kt | 6 +- .../data/notification/NotificationReceiver.kt | 50 +-- .../data/preference/PreferenceKeys.kt | 2 + .../data/preference/PreferencesHelper.kt | 2 + .../data/track/anilist/AnilistApi.kt | 9 +- .../tachiyomi/data/updater/UpdaterNotifier.kt | 6 +- .../tachiyomi/data/updater/UpdaterService.kt | 5 +- .../tachiyomi/extension/ExtensionUpdateJob.kt | 2 +- .../util/ExtensionInstallBroadcast.kt | 15 +- .../kanade/tachiyomi/ui/base/BaseToolbar.kt | 19 +- .../tachiyomi/ui/base/CenteredToolbar.kt | 5 +- .../tachiyomi/ui/base/FloatingToolbar.kt | 8 +- .../tachiyomi/ui/base/MaterialMenuSheet.kt | 19 +- .../tachiyomi/ui/base/MiniSearchView.kt | 12 + .../ui/category/CategoryController.kt | 12 +- .../ui/category/ManageCategoryDialog.kt | 38 +- .../category/addtolibrary/AddCategoryItem.kt | 18 +- .../addtolibrary/SetCategoriesSheet.kt | 154 +++++--- .../tachiyomi/ui/download/DownloadButton.kt | 24 +- .../ui/extension/ExtensionBottomSheet.kt | 10 +- .../tachiyomi/ui/extension/ExtensionHolder.kt | 5 + .../ui/extension/ExtensionTrustDialog.kt | 14 +- .../extension/SettingsExtensionsController.kt | 4 +- .../tachiyomi/ui/library/LibraryBadge.kt | 55 ++- .../tachiyomi/ui/library/LibraryController.kt | 57 ++- .../tachiyomi/ui/library/LibraryGridHolder.kt | 15 + .../tachiyomi/ui/library/LibraryHolder.kt | 3 +- .../tachiyomi/ui/library/LibraryItem.kt | 22 +- .../ui/library/display/LibraryDisplayView.kt | 14 +- .../display/TabbedLibraryDisplaySheet.kt | 9 +- .../ui/library/filter/FilterBottomSheet.kt | 11 - .../kanade/tachiyomi/ui/main/MainActivity.kt | 62 +-- .../tachiyomi/ui/main/OverflowDialog.kt | 22 +- .../tachiyomi/ui/main/SearchActivity.kt | 9 +- .../tachiyomi/ui/manga/EditMangaDialog.kt | 27 +- .../tachiyomi/ui/manga/FullCoverDialog.kt | 11 +- .../tachiyomi/ui/manga/MangaDetailsAdapter.kt | 1 + .../ui/manga/MangaDetailsController.kt | 363 +++++++++++++----- .../ui/manga/MangaDetailsPresenter.kt | 8 +- .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 54 ++- .../ui/manga/chapter/ChapterFilterLayout.kt | 10 +- .../ui/manga/chapter/ChapterHolder.kt | 14 + .../manga/chapter/ChaptersSortBottomSheet.kt | 43 ++- .../ui/manga/track/SetTrackChaptersDialog.kt | 75 ---- .../manga/track/SetTrackReadingDatesDialog.kt | 152 -------- .../ui/manga/track/SetTrackScoreDialog.kt | 74 ---- .../ui/manga/track/SetTrackStatusDialog.kt | 62 --- .../tachiyomi/ui/manga/track/TrackAdapter.kt | 5 +- .../tachiyomi/ui/manga/track/TrackHolder.kt | 4 +- .../ui/manga/track/TrackRemoveDialog.kt | 74 ---- .../ui/manga/track/TrackingBottomSheet.kt | 287 +++++++++++--- .../ui/migration/MigrationController.kt | 6 - .../ui/migration/MigrationMangaDialog.kt | 18 +- .../ui/migration/SearchController.kt | 99 ----- .../manga/process/MigrationListController.kt | 36 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 178 ++++++--- .../ui/reader/chapter/ReaderChapterSheet.kt | 13 +- .../ui/reader/settings/ReaderFilterView.kt | 117 +++--- .../ui/reader/viewer/ReaderProgressBar.kt | 20 +- .../ui/reader/viewer/pager/PagerPageHolder.kt | 17 +- .../tachiyomi/ui/recents/RecentsController.kt | 91 ++--- .../ui/recents/RemoveHistoryDialog.kt | 37 +- .../tachiyomi/ui/setting/AboutController.kt | 13 +- .../ui/setting/SettingsAdvancedController.kt | 48 ++- .../setting/SettingsAppearanceController.kt | 9 + .../ui/setting/SettingsBackupController.kt | 68 ++-- .../ui/setting/SettingsDownloadController.kt | 48 +-- .../tachiyomi/ui/source/BrowseController.kt | 47 ++- .../ui/source/browse/BrowseSourceItem.kt | 7 +- .../ui/source/filter/TriStateItem.kt | 2 +- .../globalsearch/GlobalSearchMangaHolder.kt | 3 + .../ui/webview/BaseWebViewActivity.kt | 79 ++-- .../tachiyomi/ui/webview/WebViewActivity.kt | 12 - .../kanade/tachiyomi/util/MangaExtensions.kt | 20 +- .../tachiyomi/util/chapter/ChapterUtil.kt | 8 +- .../util/system/ContextExtensions.kt | 16 + .../tachiyomi/util/system/DateExtensions.kt | 52 +++ .../system/MaterialAlertDialogExtensions.kt | 138 +++++++ .../util/system/WindowInsetsExtensions.kt | 15 +- .../util/view/ControllerExtensions.kt | 65 +++- .../util/view/MaterialDialogExtensions.kt | 12 - .../tachiyomi/util/view/ViewExtensions.kt | 40 +- .../tachiyomi/widget/E2EBottomSheetDialog.kt | 31 +- .../eu/kanade/tachiyomi/widget/EmptyView.kt | 16 +- .../tachiyomi/widget/NegativeSeekBar.kt | 73 ---- .../tachiyomi/widget/SimpleSeekBarListener.kt | 13 - .../tachiyomi/widget/TabbedBottomSheet.kt | 6 +- .../tachiyomi/widget/TriStateCheckBox.kt | 141 +++++-- .../materialdialogs/CustomDialogTitle.kt | 42 ++ .../MaterialDialogMultiChoiceExt.kt | 26 -- .../materialdialogs/QuadStateCheckBox.kt | 48 --- .../QuadStateMultiChoiceDialogAdapter.kt | 192 --------- .../TriStateMultiChoiceDialogAdapter.kt | 145 +++++++ ...er.kt => TriStateMultiChoiceViewHolder.kt} | 17 +- .../widget/preference/IntListMatPreference.kt | 22 +- .../widget/preference/IntListPreference.kt | 26 -- .../widget/preference/ListMatPreference.kt | 17 +- .../preference/LoginDialogPreference.kt | 16 +- .../widget/preference/MatPreference.kt | 27 +- .../preference/MultiListMatPreference.kt | 68 ++-- .../preference/SwitchPreferenceCategory.kt | 3 - .../widget/preference/TrackLogoutDialog.kt | 12 +- .../preference/TriStateListPreference.kt | 34 +- .../res/color/bottom_nav_item_selector.xml | 10 +- .../res/color/bottom_nav_text_selector.xml | 8 + .../library_comfortable_grid_foreground.xml | 6 + .../library_comfortable_subtitle_selector.xml | 5 + .../library_comfortable_title_selector.xml | 5 + .../res/color/library_grid_foreground.xml | 3 +- .../res/color/library_stroke_selector.xml | 5 + app/src/main/res/color/ripple_nav.xml | 2 +- .../main/res/color/secondary_container.xml | 4 + .../res/color/slider_active_track_color.xml | 5 + .../res/color/slider_inactive_track_color.xml | 5 + .../main/res/color/slider_tick_inactive.xml | 4 + app/src/main/res/color/switch_thumb_tint.xml | 11 + .../res/color/text_btn_color_selector.xml | 7 + app/src/main/res/drawable/action_mode_bg.xml | 17 - ...heck_box_checked_to_indeterminate_24dp.xml | 80 ++++ .../anim_checkbox_blank_to_x_24dp.xml | 80 ++++ ...m_checkbox_indeterminate_to_blank_24dp.xml | 80 ++++ .../main/res/drawable/card_item_selector.xml | 2 +- .../ic_check_box_indeterminate_24dp.xml | 8 + ... => library_comfortable_grid_selector.xml} | 26 +- ...rary_comfortable_grid_selector_overlay.xml | 14 + .../library_compact_grid_selector.xml | 28 +- .../library_compact_grid_selector_overlay.xml | 39 +- ...rary_confortable_grid_selector_overlay.xml | 22 -- .../res/drawable/reader_toolbar_ripple.xml | 4 +- app/src/main/res/drawable/rounded_ripple.xml | 26 +- .../res/drawable/tab_highlight_indicator.xml | 6 +- .../layout-sw600dp-land/manga_header_item.xml | 20 +- .../layout-sw600dp-port/manga_header_item.xml | 20 +- .../main/res/layout-w720dp/main_activity.xml | 35 +- app/src/main/res/layout/add_category_item.xml | 6 +- app/src/main/res/layout/bottom_menu_sheet.xml | 4 +- app/src/main/res/layout/browse_controller.xml | 17 - .../main/res/layout/catergory_text_view.xml | 2 +- .../main/res/layout/chapter_filter_layout.xml | 2 +- .../main/res/layout/chapter_header_item.xml | 2 +- .../res/layout/chapter_sort_bottom_sheet.xml | 4 +- app/src/main/res/layout/chapters_item.xml | 4 +- app/src/main/res/layout/common_view_empty.xml | 2 +- .../layout/custom_dialog_title_message.xml | 35 ++ app/src/main/res/layout/dialog_quadstate.xml | 51 +++ .../main/res/layout/download_bottom_sheet.xml | 2 +- app/src/main/res/layout/download_item.xml | 6 +- app/src/main/res/layout/edit_manga_dialog.xml | 308 ++++++++------- .../main/res/layout/extension_card_header.xml | 4 +- .../main/res/layout/extension_card_item.xml | 14 +- .../res/layout/extension_detail_header.xml | 4 +- .../res/layout/extensions_bottom_sheet.xml | 1 - app/src/main/res/layout/filter_tag_group.xml | 8 +- app/src/main/res/layout/genre_chip.xml | 3 +- .../layout/library_category_header_item.xml | 6 +- .../main/res/layout/library_controller.xml | 31 +- .../res/layout/library_display_layout.xml | 3 +- .../res/layout/listitem_tristatechoice.xml | 24 ++ app/src/main/res/layout/main_activity.xml | 37 +- .../main/res/layout/manga_category_dialog.xml | 2 + app/src/main/res/layout/manga_grid_item.xml | 42 +- app/src/main/res/layout/manga_header_item.xml | 30 +- app/src/main/res/layout/manga_list_item.xml | 5 +- .../main/res/layout/material_spinner_view.xml | 4 +- .../md_listitem_quadstatemultichoice.xml | 15 - .../res/layout/migration_bottom_sheet.xml | 13 +- .../main/res/layout/migration_card_item.xml | 2 +- .../main/res/layout/navigation_view_group.xml | 2 +- .../main/res/layout/navigation_view_radio.xml | 2 +- .../main/res/layout/navigation_view_text.xml | 2 +- .../res/layout/pre_migration_controller.xml | 1 + .../main/res/layout/pref_account_login.xml | 2 +- app/src/main/res/layout/reader_activity.xml | 2 +- .../main/res/layout/reader_chapter_item.xml | 6 +- .../main/res/layout/reader_color_filter.xml | 117 +++--- .../main/res/layout/reader_general_layout.xml | 4 +- app/src/main/res/layout/reader_nav.xml | 1 - .../layout/recent_chapters_section_item.xml | 5 +- app/src/main/res/layout/recent_manga_item.xml | 6 +- .../main/res/layout/recents_controller.xml | 27 -- .../main/res/layout/recents_footer_item.xml | 4 +- .../main/res/layout/recents_header_item.xml | 3 +- .../res/layout/rounded_category_hopper.xml | 13 +- .../main/res/layout/set_categories_sheet.xml | 2 +- .../settings_search_controller_card.xml | 2 +- app/src/main/res/layout/sort_text_view.xml | 7 +- .../source_global_search_controller_card.xml | 21 +- ...rce_global_search_controller_card_item.xml | 14 +- .../main/res/layout/source_header_item.xml | 2 +- .../main/res/layout/tachi_overflow_layout.xml | 10 +- app/src/main/res/layout/theme_item.xml | 4 +- app/src/main/res/layout/track_item.xml | 177 +++++---- app/src/main/res/layout/track_search_item.xml | 16 +- .../main/res/layout/tracking_bottom_sheet.xml | 2 +- .../main/res/layout/tri_state_check_box.xml | 10 +- .../main/res/layout/unread_download_badge.xml | 8 +- app/src/main/res/layout/webview_activity.xml | 2 +- app/src/main/res/values-night-v31/themes.xml | 13 +- app/src/main/res/values-night/colors.xml | 22 +- app/src/main/res/values-night/styles.xml | 2 +- app/src/main/res/values-night/themes.xml | 14 +- app/src/main/res/values-v31/themes.xml | 18 +- app/src/main/res/values/attrs.xml | 11 +- app/src/main/res/values/colors.xml | 38 +- app/src/main/res/values/strings.xml | 7 +- app/src/main/res/values/styles.xml | 217 +++++------ app/src/main/res/values/themes.xml | 93 +++-- buildSrc/src/main/kotlin/Dependencies.kt | 3 - 214 files changed, 3637 insertions(+), 2937 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackRemoveDialog.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/view/MaterialDialogExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/CustomDialogTitle.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceDialogAdapter.kt rename app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/{QuadStateMultiChoiceViewHolder.kt => TriStateMultiChoiceViewHolder.kt} (54%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt create mode 100644 app/src/main/res/color/bottom_nav_text_selector.xml create mode 100644 app/src/main/res/color/library_comfortable_grid_foreground.xml create mode 100644 app/src/main/res/color/library_comfortable_subtitle_selector.xml create mode 100644 app/src/main/res/color/library_comfortable_title_selector.xml create mode 100644 app/src/main/res/color/library_stroke_selector.xml create mode 100644 app/src/main/res/color/secondary_container.xml create mode 100644 app/src/main/res/color/slider_active_track_color.xml create mode 100644 app/src/main/res/color/slider_inactive_track_color.xml create mode 100644 app/src/main/res/color/slider_tick_inactive.xml create mode 100644 app/src/main/res/color/switch_thumb_tint.xml create mode 100644 app/src/main/res/color/text_btn_color_selector.xml delete mode 100644 app/src/main/res/drawable/action_mode_bg.xml create mode 100644 app/src/main/res/drawable/anim_check_box_checked_to_indeterminate_24dp.xml create mode 100644 app/src/main/res/drawable/anim_checkbox_blank_to_x_24dp.xml create mode 100644 app/src/main/res/drawable/anim_checkbox_indeterminate_to_blank_24dp.xml create mode 100644 app/src/main/res/drawable/ic_check_box_indeterminate_24dp.xml rename app/src/main/res/drawable/{library_confortable_grid_selector.xml => library_comfortable_grid_selector.xml} (52%) create mode 100644 app/src/main/res/drawable/library_comfortable_grid_selector_overlay.xml delete mode 100644 app/src/main/res/drawable/library_confortable_grid_selector_overlay.xml create mode 100644 app/src/main/res/layout/custom_dialog_title_message.xml create mode 100644 app/src/main/res/layout/dialog_quadstate.xml create mode 100644 app/src/main/res/layout/listitem_tristatechoice.xml delete mode 100644 app/src/main/res/layout/md_listitem_quadstatemultichoice.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8615f2b61a..ebe447953d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -99,9 +99,9 @@ dependencies { implementation("tachiyomi.sourceapi:source-api:1.1") // Android X libraries - implementation("androidx.appcompat:appcompat:1.3.1") + implementation("androidx.appcompat:appcompat:1.4.0-alpha03") implementation("androidx.cardview:cardview:1.0.0") - implementation("com.google.android.material:material:1.4.0") + implementation("com.google.android.material:material:1.5.0-alpha04") implementation("androidx.webkit:webkit:1.4.0") implementation("androidx.recyclerview:recyclerview:1.2.1") implementation("androidx.preference:preference:1.1.1") @@ -139,24 +139,10 @@ dependencies { implementation("com.squareup.okio:okio:2.10.0") // Chucker - val chuckerVersion = "3.2.0" + val chuckerVersion = "3.5.2" debugImplementation("com.github.ChuckerTeam.Chucker:library:$chuckerVersion") releaseImplementation("com.github.ChuckerTeam.Chucker:library-no-op:$chuckerVersion") - // hyperion - debugImplementation("com.willowtreeapps.hyperion:hyperion-core:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-timber:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-core:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-attr:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-build-config:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-crash:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-disk:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-geiger-counter:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-measurement:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-phoenix:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-recorder:${Versions.HYPERION}") - debugImplementation("com.willowtreeapps.hyperion:hyperion-shared-preferences:${Versions.HYPERION}") - // REST implementation("com.squareup.retrofit2:retrofit:${Versions.RETROFIT}") implementation("com.squareup.retrofit2:converter-gson:${Versions.RETROFIT}") @@ -182,7 +168,7 @@ dependencies { implementation("org.jsoup:jsoup:1.13.1") // Job scheduling - implementation("androidx.work:work-runtime-ktx:2.5.0") + implementation("androidx.work:work-runtime-ktx:2.6.0") implementation("com.google.android.gms:play-services-gcm:17.0.0") @@ -222,10 +208,6 @@ dependencies { implementation("eu.davidea:flexible-adapter:5.1.0") implementation("eu.davidea:flexible-adapter-ui:1.0.0") implementation("com.nononsenseapps:filepicker:2.5.2") - implementation("com.afollestad.material-dialogs:core:${Versions.materialDialogs}") - implementation("com.afollestad.material-dialogs:input:${Versions.materialDialogs}") - implementation("com.afollestad.material-dialogs:datetime:${Versions.materialDialogs}") - implementation("me.zhanghai.android.systemuihelper:library:1.0.0") implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0") implementation("com.github.mthli:Slice:v1.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7647d85d24..c146f72d60 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,7 @@ android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true" + android:supportsRtl="true" android:roundIcon="@mipmap/ic_launcher_round" android:label="@string/app_name" android:largeHeap="true" @@ -42,7 +43,8 @@ android:name=".ui.main.MainActivity" android:windowSoftInputMode="adjustNothing" android:label="@string/app_short_name" - android:theme="@style/Theme.Splash"> + android:theme="@style/Theme.Splash" + android:exported="true"> @@ -52,7 +54,8 @@ + android:label="@string/label_global_search" + android:exported="true"> @@ -71,7 +74,8 @@ + android:theme="@style/Theme.Splash" + android:exported="true"> @@ -119,7 +123,8 @@ android:theme="@style/FilePickerTheme" /> + android:label="MyAnimeList" + android:exported="true"> @@ -133,7 +138,8 @@ + android:label="Anilist" + android:exported="true"> @@ -147,7 +153,8 @@ + android:label="Shikimori" + android:exported="true"> @@ -161,7 +168,8 @@ + android:label="Bangumi" + android:exported="true"> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 412d8389a3..4b0c3e921d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -269,6 +269,12 @@ interface Manga : SManga { get() = viewer_flags and OrientationType.MASK set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK) + var vibrantCoverColor: Int? + get() = vibrantCoverColorMap[id] + set(value) { + id?.let { vibrantCoverColorMap[it] = value } + } + companion object { // Generic filter that does not filter anything @@ -311,6 +317,8 @@ interface Manga : SManga { const val TYPE_COMIC = 4 const val TYPE_WEBTOON = 5 + private val vibrantCoverColorMap: HashMap = hashMapOf() + fun create(source: Long): Manga = MangaImpl().apply { this.source = source } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index 21f722c168..4723f851b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -248,7 +248,7 @@ internal class DownloadNotifier(private val context: Context) { context, 0, customIntent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) ) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt index 42761a863c..80d8a10783 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.data.image.coil import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.widget.ImageView +import androidx.palette.graphics.Palette import coil.ImageLoader import coil.imageLoader import coil.memory.MemoryCache @@ -20,6 +22,18 @@ class LibraryMangaImageTarget( private val coverCache: CoverCache by injectLazy() + override fun onSuccess(result: Drawable) { + super.onSuccess(result) + if (manga.vibrantCoverColor == null) { + val bitmap = (drawable as? BitmapDrawable)?.bitmap ?: return + Palette.from(bitmap).generate { + if (it == null) return@generate + val color = it.getBestColor() ?: return@generate + manga.vibrantCoverColor = color + } + } + } + override fun onError(error: Drawable?) { super.onError(error) if (manga.favorite) { @@ -52,3 +66,20 @@ inline fun ImageView.loadManga( .build() return imageLoader.enqueue(request) } + +fun Palette.getBestColor(): Int? { + val vibPopulation = vibrantSwatch?.population ?: -1 + val domLum = dominantSwatch?.hsl?.get(2) ?: -1f + val mutedPopulation = mutedSwatch?.population ?: -1 + val mutedSaturationLimit = if (mutedPopulation > vibPopulation * 3f) 0.1f else 0.25f + return when { + (dominantSwatch?.hsl?.get(1) ?: 0f) >= .25f && + domLum <= .8f && domLum > .2f -> dominantSwatch?.rgb + vibPopulation >= mutedPopulation * 0.75f -> vibrantSwatch?.rgb + mutedPopulation > vibPopulation * 1.5f && + (mutedSwatch?.hsl?.get(1) ?: 0f) > mutedSaturationLimit -> mutedSwatch?.rgb + else -> arrayListOf(vibrantSwatch, lightVibrantSwatch, darkVibrantSwatch).maxByOrNull { + if (it === vibrantSwatch) (it?.population ?: -1) * 3 else it?.population ?: -1 + }?.rgb + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 1402580bda..07b0ba3d9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -267,7 +267,7 @@ class LibraryUpdateNotifier(private val context: Context) { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP action = MainActivity.SHORTCUT_RECENTLY_UPDATED } - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt index e43f3b1e1b..6b8a8da1ef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt @@ -21,7 +21,7 @@ object NotificationHandler { val intent = Intent(context, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP intent.action = MainActivity.SHORTCUT_DOWNLOADS - return PendingIntent.getActivity(context, -201, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getActivity(context, -201, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -36,7 +36,7 @@ object NotificationHandler { setDataAndType(uri, "image/*") flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION } - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -50,6 +50,6 @@ object NotificationHandler { setDataAndType(uri, "application/vnd.android.package-archive") flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION } - return PendingIntent.getActivity(context, 0, intent, 0) + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index c9ccbd1c30..64196c0031 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -318,7 +318,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_RESUME_DOWNLOADS } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -331,7 +331,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_PAUSE_DOWNLOADS } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -344,7 +344,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_CLEAR_DOWNLOADS } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -359,7 +359,7 @@ class NotificationReceiver : BroadcastReceiver() { action = ACTION_DISMISS_NOTIFICATION putExtra(EXTRA_NOTIFICATION_ID, notificationId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -412,8 +412,7 @@ class NotificationReceiver : BroadcastReceiver() { context, 0, shareIntent, - PendingIntent - .FLAG_CANCEL_CURRENT + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } @@ -431,7 +430,7 @@ class NotificationReceiver : BroadcastReceiver() { putExtra(EXTRA_FILE_LOCATION, path) putExtra(EXTRA_NOTIFICATION_ID, notificationId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -452,8 +451,7 @@ class NotificationReceiver : BroadcastReceiver() { context, manga.id.hashCode(), newIntent, - PendingIntent - .FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } @@ -475,7 +473,7 @@ class NotificationReceiver : BroadcastReceiver() { context, downloadLink.hashCode(), newIntent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } @@ -497,7 +495,7 @@ class NotificationReceiver : BroadcastReceiver() { context, manga.id.hashCode(), newIntent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } @@ -514,7 +512,7 @@ class NotificationReceiver : BroadcastReceiver() { setDataAndType(uri, "text/plain") flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION } - return PendingIntent.getActivity(context, 0, intent, 0) + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } /** @@ -531,22 +529,10 @@ class NotificationReceiver : BroadcastReceiver() { context, 0, newIntent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } - /**Returns the PendingIntent that will open the error log in an external text viewer - * - */ - internal fun openFileExplorerPendingActivity(context: Context, uri: Uri): PendingIntent { - val toLaunch = Intent(Intent.ACTION_VIEW).apply { - setDataAndType(uri, "text/plain") - flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_GRANT_READ_URI_PERMISSION - } - return PendingIntent.getActivity(context, 0, toLaunch, 0) - } - /** * Returns [PendingIntent] that marks a chapter as read and deletes it if preferred * @@ -568,7 +554,7 @@ class NotificationReceiver : BroadcastReceiver() { putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode()) putExtra(EXTRA_GROUP_ID, groupId) } - return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -581,7 +567,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_CANCEL_LIBRARY_UPDATE } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -594,7 +580,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_CANCEL_EXTENSION_UPDATE } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -607,7 +593,7 @@ class NotificationReceiver : BroadcastReceiver() { val intent = Intent(context, NotificationReceiver::class.java).apply { action = ACTION_CANCEL_UPDATE_DOWNLOAD } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -624,7 +610,7 @@ class NotificationReceiver : BroadcastReceiver() { putExtra(EXTRA_URI, uri) putExtra(EXTRA_NOTIFICATION_ID, notificationId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -641,7 +627,7 @@ class NotificationReceiver : BroadcastReceiver() { putExtra(EXTRA_URI, uri) putExtra(EXTRA_NOTIFICATION_ID, notificationId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } /** @@ -656,7 +642,7 @@ class NotificationReceiver : BroadcastReceiver() { action = ACTION_CANCEL_RESTORE putExtra(EXTRA_NOTIFICATION_ID, notificationId) } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 75ddc034cd..c98d15b30a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -231,6 +231,8 @@ object PreferenceKeys { const val showNsfwExtension = "show_nsfw_extension" const val labelNsfwExtension = "label_nsfw_extension" + const val themeMangaDetails = "theme_manga_details" + const val incognitoMode = "incognito_mode" const val sideNavMode = "side_nav_mode" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index d65b92f965..0c510f9003 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -423,6 +423,8 @@ class PreferencesHelper(val context: Context) { fun showNsfwExtension() = flowPrefs.getBoolean(Keys.showNsfwExtension, true) fun labelNsfwExtension() = prefs.getBoolean(Keys.labelNsfwExtension, true) + fun themeMangaDetails() = prefs.getBoolean(Keys.themeMangaDetails, true) + fun dohProvider() = prefs.getInt(Keys.dohProvider, -1) fun showSeriesInShortcuts() = prefs.getBoolean(Keys.showSeriesInShortcuts, true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index f907fe90d1..b8a028b6fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -1,9 +1,6 @@ package eu.kanade.tachiyomi.data.track.anilist import androidx.core.net.toUri -import com.afollestad.date.dayOfMonth -import com.afollestad.date.month -import com.afollestad.date.year import com.github.salomonbrys.kotson.array import com.github.salomonbrys.kotson.get import com.github.salomonbrys.kotson.jsonObject @@ -247,9 +244,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { val calendar = Calendar.getInstance() calendar.timeInMillis = dateValue return jsonObject( - "year" to calendar.year, - "month" to calendar.month + 1, - "day" to calendar.dayOfMonth, + "year" to calendar.get(Calendar.YEAR), + "month" to calendar.get(Calendar.MONTH) + 1, + "day" to calendar.get(Calendar.DAY_OF_MONTH), ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt index 8da3043a36..04137241c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt @@ -68,7 +68,7 @@ internal class UpdaterNotifier(private val context: Context) { context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) ) addReleasePageAction() @@ -84,7 +84,7 @@ internal class UpdaterNotifier(private val context: Context) { addAction( R.drawable.ic_new_releases_24dp, context.getString(R.string.release_page), - PendingIntent.getActivity(context, releaseUrl.hashCode(), releaseIntent, PendingIntent.FLAG_UPDATE_CURRENT) + PendingIntent.getActivity(context, releaseUrl.hashCode(), releaseIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) ) } } @@ -175,7 +175,7 @@ internal class UpdaterNotifier(private val context: Context) { context, 0, context.packageManager.getLaunchIntentForPackage(BuildConfig.APPLICATION_ID), - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) setContentIntent(pendingIntent) clearActions() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt index 81e9189aa1..fb4b9db7fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageInstaller import android.os.Build import android.os.IBinder import android.os.PowerManager +import androidx.annotation.RequiresApi import androidx.core.content.edit import androidx.preference.PreferenceManager import eu.kanade.tachiyomi.BuildConfig @@ -166,9 +167,9 @@ class UpdaterService : Service() { } } + @RequiresApi(31) private fun startInstalling(file: File, notifyOnInstall: Boolean) { try { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return val packageInstaller = packageManager.packageInstaller val data = file.inputStream() @@ -266,7 +267,7 @@ class UpdaterService : Service() { putExtra(EXTRA_DOWNLOAD_URL, url) putExtra(EXTRA_NOTIFY_ON_INSTALL, notifyOnInstall) } - return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt index fa0005257b..da053865ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt @@ -122,7 +122,7 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam ) { val intent = ExtensionInstallService.jobIntent(context, extensions) val pendingIntent = - PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) addAction( R.drawable.ic_file_download_24dp, context.getString(R.string.update_all), diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt index d8e4702243..1fcec5fce6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt @@ -54,7 +54,12 @@ class ExtensionInstallBroadcast : BroadcastReceiver() { .putExtra(ExtensionInstaller.EXTRA_DOWNLOAD_ID, downloadId) .putExtra(EXTRA_SESSION_ID, sessionId) - val pendingIntent = PendingIntent.getBroadcast(context, downloadId.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val mutableFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 + } + val pendingIntent = PendingIntent.getBroadcast(context, downloadId.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or mutableFlag) val statusReceiver = pendingIntent.intentSender session.commit(statusReceiver) val extensionManager: ExtensionManager by injectLazy() @@ -147,8 +152,12 @@ class ExtensionInstallActivity : Activity() { .setAction(PACKAGE_INSTALLED_ACTION) .putExtra(ExtensionInstaller.EXTRA_DOWNLOAD_ID, downloadId) .putExtra(EXTRA_SESSION_ID, sessionId) - - val pendingIntent = PendingIntent.getActivity(this, downloadId.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val mutableFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 + } + val pendingIntent = PendingIntent.getActivity(this, downloadId.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or mutableFlag) val statusReceiver = pendingIntent.intentSender session.commit(statusReceiver) val extensionManager: ExtensionManager by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/BaseToolbar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/BaseToolbar.kt index a0b5797940..f13656f65e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/BaseToolbar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/BaseToolbar.kt @@ -4,16 +4,22 @@ import android.content.Context import android.util.AttributeSet import android.widget.TextView import androidx.annotation.DrawableRes -import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.core.view.isVisible +import com.bluelinelabs.conductor.Router import com.google.android.material.appbar.MaterialToolbar import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.main.SearchActivity open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : MaterialToolbar(context, attrs) { - protected lateinit var toolbarTitle: TextView - private val defStyleRes = com.google.android.material.R.style.Widget_MaterialComponents_Toolbar + var router: Router? = null + val onRoot: Boolean + get() = router?.backstackSize ?: 1 <= 1 && context !is SearchActivity + + lateinit var toolbarTitle: TextView + protected set + private val defStyleRes = com.google.android.material.R.style.Widget_Material3_Toolbar protected val titleTextAppearance: Int @@ -37,6 +43,11 @@ open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: Attrib setCustomTitle(title) } + override fun setTitleTextColor(color: Int) { + super.setTitleTextColor(color) + if (::toolbarTitle.isInitialized) toolbarTitle.setTextColor(color) + } + protected open fun setCustomTitle(title: CharSequence?) { toolbarTitle.isVisible = true toolbarTitle.text = title @@ -69,7 +80,7 @@ open class BaseToolbar @JvmOverloads constructor(context: Context, attrs: Attrib @DrawableRes private fun getDropdownRes(): Int { return when { - incognito && navigationIcon !is DrawerArrowDrawable -> R.drawable.ic_blank_28dp + incognito && onRoot -> R.drawable.ic_blank_28dp else -> 0 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/CenteredToolbar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/CenteredToolbar.kt index 02b29163f2..f8f544702a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/CenteredToolbar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/CenteredToolbar.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.Gravity -import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.core.view.updateLayoutParams import com.google.android.material.textview.MaterialTextView import eu.kanade.tachiyomi.R @@ -29,8 +28,8 @@ class CenteredToolbar@JvmOverloads constructor(context: Context, attrs: Attribut override fun setCustomTitle(title: CharSequence?) { super.setCustomTitle(title) toolbarTitle.updateLayoutParams { - gravity = if (navigationIcon is DrawerArrowDrawable) Gravity.START else Gravity.CENTER + gravity = if (!onRoot) Gravity.START else Gravity.CENTER } - toolbarTitle.compoundDrawablePadding = if (navigationIcon is DrawerArrowDrawable) 6.dpToPx else 0 + toolbarTitle.compoundDrawablePadding = if (!onRoot) 6.dpToPx else 0 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/FloatingToolbar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/FloatingToolbar.kt index 37abdbf623..48bf3f06de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/FloatingToolbar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/FloatingToolbar.kt @@ -22,8 +22,8 @@ class FloatingToolbar @JvmOverloads constructor(context: Context, attrs: Attribu private lateinit var toolbarsubTitle: TextView private lateinit var cardIncogImage: ImageView - private val defStyleRes = com.google.android.material.R.style.Widget_MaterialComponents_Toolbar - private val subtitleTextAppeance: Int + private val defStyleRes = com.google.android.material.R.style.Widget_Material3_Toolbar + private val subtitleTextAppearance: Int init { val a = context.obtainStyledAttributes( @@ -32,7 +32,7 @@ class FloatingToolbar @JvmOverloads constructor(context: Context, attrs: Attribu 0, defStyleRes ) - subtitleTextAppeance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0) + subtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0) a.recycle() } override fun onFinishInflate() { @@ -42,7 +42,7 @@ class FloatingToolbar @JvmOverloads constructor(context: Context, attrs: Attribu toolbarTitle.setTextColor(actionColorAlpha) toolbarsubTitle = findViewById(R.id.card_subtitle) - toolbarsubTitle.setTextAppearance(subtitleTextAppeance) + toolbarsubTitle.setTextAppearance(subtitleTextAppearance) toolbarsubTitle.setTextColor(actionColorAlphaSecondary) toolbarsubTitle.isVisible = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/MaterialMenuSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/MaterialMenuSheet.kt index cff0b525ee..efd79426b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/MaterialMenuSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/MaterialMenuSheet.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.base -import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.app.Activity +import android.content.res.ColorStateList import android.os.Build import android.view.LayoutInflater import android.view.View @@ -15,8 +15,10 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.BottomMenuSheetBinding import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.hasSideNavBar import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener @@ -103,12 +105,17 @@ class MaterialMenuSheet( elevationAnimator?.cancel() isElevated = elevate elevationAnimator?.cancel() - elevationAnimator = ObjectAnimator.ofFloat( - binding.titleLayout, - "elevation", - binding.titleLayout.elevation, - if (elevate) 5f else 0f + val nonElevateColor = activity.getResourceColor(R.attr.colorSurface) + val elevateColor = activity.getResourceColor(R.attr.colorPrimaryVariant) + + elevationAnimator = ValueAnimator.ofArgb( + if (elevate) nonElevateColor else elevateColor, + if (elevate) elevateColor else nonElevateColor ) + + elevationAnimator?.addUpdateListener { + binding.titleLayout.backgroundTintList = ColorStateList.valueOf(it.animatedValue as Int) + } elevationAnimator?.start() } elevate(binding.menuSheetRecycler.canScrollVertically(-1)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/MiniSearchView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/MiniSearchView.kt index 2d68043349..dce70be963 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/MiniSearchView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/MiniSearchView.kt @@ -45,6 +45,18 @@ class MiniSearchView @JvmOverloads constructor(context: Context, attrs: Attribut searchMagIconImageView?.layoutParams = LinearLayout.LayoutParams(0, 0) } + fun tintBar(color: Int) { + val clearButton = findViewById(androidx.appcompat.R.id.search_close_btn) + clearButton?.imageTintList = ColorStateList.valueOf(color) + + val actionColorAlpha = + ColorUtils.setAlphaComponent(color, 200) + val searchTextView = + findViewById(androidx.appcompat.R.id.search_src_text) + searchTextView?.setHintTextColor(actionColorAlpha) + searchTextView?.setTextColor(actionColorAlpha) + } + override fun onAttachedToWindow() { super.onAttachedToWindow() scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt index 8205bc457c..6961e1d766 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.afollestad.materialdialogs.MaterialDialog import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar import eu.davidea.flexibleadapter.FlexibleAdapter @@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Companion.CREATE_CATEGORY_ORDER import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.liftAppbarWith import eu.kanade.tachiyomi.util.view.snack @@ -124,13 +124,13 @@ class CategoryController(bundle: Bundle? = null) : } override fun onItemDelete(position: Int) { - MaterialDialog(activity!!) - .title(R.string.confirm_category_deletion) - .message(R.string.confirm_category_deletion_message) - .positiveButton(R.string.delete) { + activity!!.materialAlertDialog() + .setTitle(R.string.confirm_category_deletion) + .setMessage(R.string.confirm_category_deletion_message) + .setPositiveButton(R.string.delete) { _, _ -> deleteCategory(position) } - .negativeButton(android.R.string.no) + .setNegativeButton(android.R.string.cancel, null) .show() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt index b27dc5162d..aafa5a7196 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/ManageCategoryDialog.kt @@ -2,13 +2,11 @@ package eu.kanade.tachiyomi.ui.category import android.app.Activity import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.callbacks.onShow -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.customview.getCustomView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tfcporciuncula.flow.Preference import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -19,6 +17,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.databinding.MangaCategoryDialogBinding import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.library.LibrarySort +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.widget.TriStateCheckBox import uy.kohesive.injekt.injectLazy @@ -39,31 +38,36 @@ class ManageCategoryDialog(bundle: Bundle? = null) : lateinit var binding: MangaCategoryDialogBinding override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val dialog = dialog(activity!!) - binding = MangaCategoryDialogBinding.bind(dialog.getCustomView()) + val dialog = dialog(activity!!).create() onViewCreated() + dialog.setOnShowListener { + dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.setOnClickListener { + if (onPositiveButtonClick()) { + dialog.dismiss() + } + } + } return dialog } - fun dialog(activity: Activity): MaterialDialog { - return MaterialDialog(activity).apply { - title(if (category == null) R.string.new_category else R.string.manage_category) - customView(viewRes = R.layout.manga_category_dialog) - negativeButton(android.R.string.cancel) { dismiss() } - positiveButton(R.string.save) { + fun dialog(activity: Activity): MaterialAlertDialogBuilder { + return activity.materialAlertDialog().apply { + setTitle(if (category == null) R.string.new_category else R.string.manage_category) + binding = MangaCategoryDialogBinding.inflate(activity.layoutInflater) + setView(binding.root) + setNegativeButton(android.R.string.cancel, null) + setPositiveButton(R.string.save) { dialog, _ -> if (onPositiveButtonClick()) { - dismiss() + dialog.dismiss() } } - noAutoDismiss() } } fun show(activity: Activity) { - val dialog = dialog(activity) - binding = MangaCategoryDialogBinding.bind(dialog.getCustomView()) + val dialog = dialog(activity).create() onViewCreated() - dialog.onShow { + dialog.setOnShowListener { binding.title.requestFocus() } dialog.show() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/AddCategoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/AddCategoryItem.kt index 6a9f4d7f8f..225de86294 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/AddCategoryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/AddCategoryItem.kt @@ -6,6 +6,7 @@ import com.mikepenz.fastadapter.items.AbstractItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.databinding.AddCategoryItemBinding +import eu.kanade.tachiyomi.widget.TriStateCheckBox class AddCategoryItem(val category: Category) : AbstractItem>() { @@ -17,6 +18,13 @@ class AddCategoryItem(val category: Category) : AbstractItem { return ViewHolder(v) } @@ -24,13 +32,19 @@ class AddCategoryItem(val category: Category) : AbstractItem(view) { val binding = AddCategoryItemBinding.bind(view) + + init { + binding.categoryCheckbox.useIndeterminateForInverse = true + } + override fun bindView(item: AddCategoryItem, payloads: List) { + binding.categoryCheckbox.skipInversed = item.skipInversed binding.categoryCheckbox.text = item.category.name - binding.categoryCheckbox.isChecked = item.isSelected + binding.categoryCheckbox.state = item.state } override fun unbindView(item: AddCategoryItem) { - binding.categoryCheckbox.text = null + binding.categoryCheckbox.text = "" binding.categoryCheckbox.isChecked = false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/SetCategoriesSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/SetCategoriesSheet.kt index 352699da48..26f4955df0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/SetCategoriesSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/addtolibrary/SetCategoriesSheet.kt @@ -5,16 +5,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.updateLayoutParams import androidx.core.view.updatePaddingRelative import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.ISelectionListener import com.mikepenz.fastadapter.adapters.ItemAdapter -import com.mikepenz.fastadapter.select.SelectExtension -import com.mikepenz.fastadapter.select.getSelectExtension import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category @@ -23,10 +21,11 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.databinding.SetCategoriesSheetBinding import eu.kanade.tachiyomi.ui.category.ManageCategoryDialog import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog +import eu.kanade.tachiyomi.widget.TriStateCheckBox import uy.kohesive.injekt.injectLazy -import java.util.ArrayList import java.util.Date import java.util.Locale import kotlin.math.max @@ -35,22 +34,59 @@ class SetCategoriesSheet( private val activity: Activity, private val listManga: List, var categories: MutableList, - var preselected: Array, + var preselected: Array, private val addingToLibrary: Boolean, val onMangaAdded: (() -> Unit) = { } ) : E2EBottomSheetDialog(activity) { - constructor(activity: Activity, manga: Manga, categories: MutableList, preselected: Array, addingToLibrary: Boolean, onMangaAdded: () -> Unit) : - this(activity, listOf(manga), categories, preselected, addingToLibrary, onMangaAdded) + constructor( + activity: Activity, + manga: Manga, + categories: MutableList, + preselected: Array, + addingToLibrary: Boolean, + onMangaAdded: () -> Unit + ) : this( + activity, listOf(manga), categories, + categories.map { + if (it.id in preselected) { + TriStateCheckBox.State.CHECKED + } else { + TriStateCheckBox.State.UNCHECKED + } + }.toTypedArray(), + addingToLibrary, onMangaAdded + ) private val fastAdapter: FastAdapter private val itemAdapter = ItemAdapter() - private val selectExtension: SelectExtension + private val db: DatabaseHelper by injectLazy() override var recyclerView: RecyclerView? = binding.categoryRecyclerView + private val preCheckedCategories = categories.mapIndexedNotNull { index, category -> + category.takeIf { preselected[index] == TriStateCheckBox.State.CHECKED } + } + private val preIndeterminateCategories = categories.mapIndexedNotNull { index, category -> + category.takeIf { preselected[index] == TriStateCheckBox.State.INDETERMINATE } + } + private val selectedCategories = preIndeterminateCategories + preCheckedCategories + + private val selectedItems: Set + get() = itemAdapter.adapterItems.filter { it.isSelected }.toSet() + + private val checkedItems: Set + get() = itemAdapter.adapterItems.filter { it.state == TriStateCheckBox.State.CHECKED }.toSet() + + private val indeterminateItems: Set + get() = itemAdapter.adapterItems.filter { it.state == TriStateCheckBox.State.INDETERMINATE }.toSet() + + private val uncheckedItems: Set + get() = itemAdapter.adapterItems.filter { !it.isSelected }.toSet() + override fun createBinding(inflater: LayoutInflater) = SetCategoriesSheetBinding.inflate(inflater) + init { binding.toolbarTitle.text = context.getString( if (addingToLibrary) { @@ -84,9 +120,9 @@ class SetCategoriesSheet( binding.titleLayout.viewTreeObserver.addOnGlobalLayoutListener { binding.categoryRecyclerView.updateLayoutParams { val fullHeight = activity.window.decorView.height - val insets = activity.window.decorView.rootWindowInsets + val insets = activity.window.decorView.rootWindowInsetsCompat matchConstraintMaxHeight = - fullHeight - (insets?.systemWindowInsetTop ?: 0) - + fullHeight - (insets?.getInsets(systemBars())?.top ?: 0) - binding.titleLayout.height - binding.buttonLayout.height - 75.dpToPx } } @@ -95,38 +131,70 @@ class SetCategoriesSheet( fastAdapter.setHasStableIds(true) binding.categoryRecyclerView.layoutManager = LinearLayoutManager(context) binding.categoryRecyclerView.adapter = fastAdapter - itemAdapter.set(categories.map(::AddCategoryItem)) - itemAdapter.adapterItems.forEach { item -> - item.isSelected = preselected.any { it == item.category.id } - } - - selectExtension = fastAdapter.getSelectExtension() - selectExtension.apply { - isSelectable = true - multiSelect = true - setCategoriesButtons() - selectionListener = object : ISelectionListener { - override fun onSelectionChanged(item: AddCategoryItem, selected: Boolean) { - setCategoriesButtons() + itemAdapter.set( + categories.mapIndexed { index, category -> + AddCategoryItem(category).apply { + skipInversed = preselected[index] != TriStateCheckBox.State.INDETERMINATE + state = preselected[index] } } + ) + setCategoriesButtons() + fastAdapter.onClickListener = onClickListener@{ view, _, item, _ -> + val checkBox = view as? TriStateCheckBox ?: return@onClickListener true + checkBox.goToNextStep() + item.state = checkBox.state + setCategoriesButtons() + true } } - fun setCategoriesButtons() { + private fun setCategoriesButtons() { + val addingMore = checkedItems.isNotEmpty() && + selectedCategories.isNotEmpty() && + selectedItems.map { it.category } + .containsAll(selectedCategories) && + checkedItems.size > preCheckedCategories.size + val nothingChanged = itemAdapter.adapterItems.map { it.state } + .toTypedArray() + .contentEquals(preselected) + val removing = selectedItems.isNotEmpty() && ( + // Check that selected items has the previous delta items + ( + selectedCategories.containsAll(indeterminateItems.map { it.category }) && + preIndeterminateCategories.size > indeterminateItems.size + ) || + // or check that checked items has the previous checked items + ( + preCheckedCategories.containsAll(checkedItems.map { it.category }) && + preCheckedCategories.size > checkedItems.size + ) + ) && + // Additional checks in case a delta item is now fully checked + preCheckedCategories.size >= checkedItems.size && + preIndeterminateCategories.size >= indeterminateItems.size + + val items = when { + addingToLibrary -> checkedItems.map { it.category } + addingMore -> checkedItems.map { it.category }.subtract(preCheckedCategories) + removing -> selectedCategories.subtract(selectedItems.map { it.category }) + nothingChanged -> selectedItems.map { it.category } + else -> checkedItems.map { it.category } + } binding.addToCategoriesButton.text = context.getString( - if (addingToLibrary) { - R.string.add_to_ - } else { - R.string.move_to_ + when { + addingToLibrary || (addingMore && !nothingChanged) -> R.string.add_to_ + removing -> R.string.remove_from_ + nothingChanged -> R.string.keep_in_ + else -> R.string.move_to_ }, - when (selectExtension.selections.size) { + when (items.size) { 0 -> context.getString(R.string.default_category).lowercase(Locale.ROOT) - 1 -> selectExtension.selectedItems.firstOrNull()?.category?.name ?: "" + 1 -> items.firstOrNull()?.name ?: "" else -> context.resources.getQuantityString( R.plurals.category_plural, - selectExtension.selections.size, - selectExtension.selections.size + items.size, + items.size ) } ) @@ -139,7 +207,7 @@ class SetCategoriesSheet( updateBottomButtons() binding.root.post { binding.categoryRecyclerView.scrollToPosition( - max(0, itemAdapter.adapterItems.indexOf(selectExtension.selectedItems.firstOrNull())) + max(0, itemAdapter.adapterItems.indexOf(selectedItems.firstOrNull())) ) } } @@ -157,7 +225,8 @@ class SetCategoriesSheet( val array = context.obtainStyledAttributes(attrsArray) val headerHeight = array.getDimensionPixelSize(0, 0) binding.buttonLayout.updatePaddingRelative( - bottom = activity.window.decorView.rootWindowInsets.systemWindowInsetBottom + bottom = activity.window.decorView.rootWindowInsetsCompat + ?.getInsets(systemBars())?.bottom ?: 0 ) binding.buttonLayout.updateLayoutParams { @@ -193,15 +262,14 @@ class SetCategoriesSheet( db.insertManga(manga).executeAsBlocking() } - val mc = ArrayList() - - val selectedCategories = selectExtension.selectedItems.map(AddCategoryItem::category) - for (manga in listManga) { - for (cat in selectedCategories) { - mc.add(MangaCategory.create(manga, cat)) - } - } - db.setMangaCategories(mc, listManga) + val addCategories = checkedItems.map(AddCategoryItem::category) + val removeCategories = uncheckedItems.map(AddCategoryItem::category) + val mangaCategories = listManga.map { manga -> + val categories = db.getCategoriesForManga(manga).executeAsBlocking() + .subtract(removeCategories).plus(addCategories).distinct() + categories.map { MangaCategory.create(manga, it) } + }.flatten() + db.setMangaCategories(mangaCategories, listManga) onMangaAdded() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadButton.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadButton.kt index 19fd2ab861..a7196ac2ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadButton.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadButton.kt @@ -19,8 +19,22 @@ import eu.kanade.tachiyomi.widget.EndAnimatorListener class DownloadButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { - private val activeColor = ColorUtils.blendARGB( - context.getResourceColor(R.attr.colorSecondary), + var colorSecondary = context.getResourceColor(R.attr.colorSecondary) + set(value) { + field = value + activeColor = ColorUtils.blendARGB( + colorSecondary, + context.getResourceColor(R.attr.background), + 0.05f + ) + downloadedColor = ColorUtils.blendARGB( + colorSecondary, + context.getResourceColor(R.attr.colorOnBackground), + 0.3f + ) + } + private var activeColor = ColorUtils.blendARGB( + colorSecondary, context.getResourceColor(R.attr.background), 0.05f ) @@ -32,8 +46,8 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut context, R.color.material_on_surface_disabled ) - private val downloadedColor = ColorUtils.blendARGB( - context.getResourceColor(R.attr.colorSecondary), + private var downloadedColor = ColorUtils.blendARGB( + colorSecondary, context.getResourceColor(R.attr.colorOnBackground), 0.3f ) @@ -69,7 +83,7 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut private var isAnimating = false private var iconAnimation: ObjectAnimator? = null - lateinit var binding: DownloadButtonBinding + private lateinit var binding: DownloadButtonBinding override fun onFinishInflate() { super.onFinishInflate() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt index 9190b577da..cc7d0f56d3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -9,7 +9,6 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.core.view.updatePaddingRelative import androidx.recyclerview.widget.RecyclerView -import com.afollestad.materialdialogs.MaterialDialog import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayout import eu.davidea.flexibleadapter.FlexibleAdapter @@ -28,6 +27,7 @@ import eu.kanade.tachiyomi.ui.migration.SourceAdapter import eu.kanade.tachiyomi.ui.migration.SourceItem import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.source.BrowseController +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets @@ -207,10 +207,10 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At override fun onUpdateAllClicked(position: Int) { if (!presenter.preferences.hasPromptedBeforeUpdateAll().get()) { - MaterialDialog(controller.activity!!) - .title(R.string.update_all) - .message(R.string.some_extensions_may_prompt) - .positiveButton(android.R.string.ok) { + controller.activity!!.materialAlertDialog() + .setTitle(R.string.update_all) + .setMessage(R.string.some_extensions_may_prompt) + .setPositiveButton(android.R.string.ok) { _, _ -> presenter.preferences.hasPromptedBeforeUpdateAll().set(true) updateAllExtensions(position) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index 5f6cb81a13..d78c18e682 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.extension +import android.animation.AnimatorInflater import android.content.res.ColorStateList import android.graphics.Color import android.view.View @@ -129,6 +130,8 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : val extension = item.extension val installStep = item.installStep strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) + rippleColor = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlHighlight)) + stateListAnimator = null if (installStep != null) { setText( when (installStep) { @@ -149,6 +152,8 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : when { extension.hasUpdate -> { isActivated = true + stateListAnimator = AnimatorInflater.loadStateListAnimator(context, R.animator.icon_btn_state_list_anim) + rippleColor = ColorStateList.valueOf(context.getColor(R.color.on_secondary_highlight)) setText(R.string.update) } else -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt index 747469b3f4..5917bef088 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt @@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.ui.extension import android.app.Dialog import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.util.system.materialAlertDialog class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) where T : ExtensionTrustDialog.Listener { @@ -20,15 +20,15 @@ class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) } override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialDialog(activity!!) - .title(R.string.untrusted_extension) - .message(R.string.untrusted_extension_message) - .positiveButton(R.string.trust) { + return activity!!.materialAlertDialog() + .setTitle(R.string.untrusted_extension) + .setMessage(R.string.untrusted_extension_message) + .setPositiveButton(R.string.trust) { _, _ -> listener.trustSignature(args.getString(SIGNATURE_KEY)!!) } - .negativeButton(R.string.uninstall) { + .setNegativeButton(R.string.uninstall) { _, _ -> listener.uninstallExtension(args.getString(PKGNAME_KEY)!!) - } + }.create() } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt index cd57be7e13..ef731252c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.extension import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreference +import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.ui.setting.SettingsController @@ -28,7 +28,7 @@ class SettingsExtensionsController : SettingsController() { } availableLangs.forEach { - SwitchPreference(context).apply { + SwitchPreferenceCompat(context).apply { preferenceScreen.addPreference(this) title = LocaleHelper.getSourceDisplayName(it, context) isPersistent = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryBadge.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryBadge.kt index 0e44f2964e..9a7714f127 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryBadge.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryBadge.kt @@ -1,15 +1,18 @@ package eu.kanade.tachiyomi.ui.library import android.content.Context +import android.content.res.ColorStateList import android.util.AttributeSet import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative import com.google.android.material.card.MaterialCardView +import com.google.android.material.shape.MaterialShapeDrawable import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.UnreadDownloadBadgeBinding import eu.kanade.tachiyomi.util.system.contextCompatColor import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.view.makeShapeCorners class LibraryBadge @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : MaterialCardView(context, attrs) { @@ -19,9 +22,16 @@ class LibraryBadge @JvmOverloads constructor(context: Context, attrs: AttributeS override fun onFinishInflate() { super.onFinishInflate() binding = UnreadDownloadBadgeBinding.bind(this) + + shapeAppearanceModel = makeShapeCorners(radius, radius) } - fun setUnreadDownload(unread: Int, downloads: Int, showTotalChapters: Boolean) { + fun setUnreadDownload( + unread: Int, + downloads: Int, + showTotalChapters: Boolean, + changeShape: Boolean + ) { // Update the unread count and its visibility. val unreadBadgeBackground = if (showTotalChapters) { @@ -48,22 +58,50 @@ class LibraryBadge @JvmOverloads constructor(context: Context, attrs: AttributeS // Update the download count or local status and its visibility. with(binding.downloadText) { isVisible = downloads == -2 || downloads > 0 - if (!isVisible) { return@with } + if (!isVisible) { + return@with + } text = if (downloads == -2) { resources.getString(R.string.local) } else { downloads.toString() } - setTextColor(context.getResourceColor(R.attr.colorOnDownloadBadge)) - setBackgroundColor(context.getResourceColor(R.attr.colorDownloadBadge)) + setTextColor(context.getResourceColor(R.attr.colorOnTertiary)) + setBackgroundColor(context.getResourceColor(R.attr.colorTertiary)) + } + + if (changeShape) { + shapeAppearanceModel = makeShapeCorners(radius, radius) + if (binding.downloadText.isVisible) { + binding.downloadText.background = + MaterialShapeDrawable(makeShapeCorners(topStart = radius)).apply { + this.fillColor = + ColorStateList.valueOf(context.getResourceColor(R.attr.colorTertiary)) + } + binding.unreadText.background = + MaterialShapeDrawable(makeShapeCorners(bottomEnd = radius)).apply { + this.fillColor = ColorStateList.valueOf(unreadBadgeBackground) + } + } else { + binding.unreadText.background = + MaterialShapeDrawable(makeShapeCorners(radius, radius)).apply { + this.fillColor = ColorStateList.valueOf(unreadBadgeBackground) + } + if (unread == -1) { + shapeAppearanceModel = shapeAppearanceModel.withCornerSize(radius) + } + } + } else { + shapeAppearanceModel = shapeAppearanceModel.withCornerSize(radius) } // Show the badge card if unread or downloads exists isVisible = binding.downloadText.isVisible || binding.unreadText.isVisible // Show the angles divider if both unread and downloads exists - binding.unreadAngle.isVisible = binding.downloadText.isVisible && binding.unreadText.isVisible + binding.unreadAngle.isVisible = + binding.downloadText.isVisible && binding.unreadText.isVisible binding.unreadAngle.setColorFilter(unreadBadgeBackground) if (binding.unreadAngle.isVisible) { @@ -76,7 +114,7 @@ class LibraryBadge @JvmOverloads constructor(context: Context, attrs: AttributeS } fun setChapters(chapters: Int?) { - setUnreadDownload(chapters ?: 0, 0, chapters != null) + setUnreadDownload(chapters ?: 0, 0, chapters != null, true) } fun setInLibrary(inLibrary: Boolean) { @@ -85,5 +123,10 @@ class LibraryBadge @JvmOverloads constructor(context: Context, attrs: AttributeS binding.unreadText.updatePaddingRelative(start = 5.dpToPx) binding.unreadText.isVisible = inLibrary binding.unreadText.text = resources.getText(R.string.in_library) + binding.unreadText.background = + MaterialShapeDrawable(makeShapeCorners(radius, radius)).apply { + this.fillColor = + ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondary)) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index ddb3397d2b..b1bb462c49 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -6,8 +6,8 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Activity import android.content.Context +import android.content.DialogInterface import android.content.Intent -import android.graphics.Color import android.os.Build import android.os.Bundle import android.os.Handler @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.view.WindowInsets import android.view.inputmethod.InputMethodManager +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView @@ -39,8 +40,6 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.github.florent37.viewtooltip.ViewTooltip @@ -87,6 +86,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.isImeVisible import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.collapse @@ -309,7 +309,6 @@ class LibraryController( binding.filterBottomSheet.filterBottomSheet.translationY = 0f } val pad = bottomBar.translationY - bottomBar.height - binding.shadow2.translationY = pad binding.filterBottomSheet.filterBottomSheet.updatePaddingRelative( bottom = max( (-pad).toInt(), @@ -435,9 +434,11 @@ class LibraryController( val icon = (activityBinding.bottomNav ?: activityBinding.sideNav)?.getItemView(R.id.nav_library) ?: return filterTooltip = ViewTooltip.on(activity, icon).autoHide(false, 0L).align(ViewTooltip.ALIGN.START) - .position(ViewTooltip.Position.TOP).text(R.string.tap_library_to_show_filters) + .position(ViewTooltip.Position.TOP) + .text(R.string.tap_library_to_show_filters) + .textColor(activity.getResourceColor(R.attr.colorOnSecondary)) .color(activity.getResourceColor(R.attr.colorSecondary)) - .textSize(TypedValue.COMPLEX_UNIT_SP, 15f).textColor(Color.WHITE).withShadow(false) + .textSize(TypedValue.COMPLEX_UNIT_SP, 15f).withShadow(false) .corner(30).arrowWidth(15).arrowHeight(15).distanceWithView(0) filterTooltip?.show() @@ -603,10 +604,12 @@ class LibraryController( updateLibrary() } preferences.updateOnRefresh().getOrDefault() == -1 -> { - MaterialDialog(activity!!).title(R.string.what_should_update) - .negativeButton(android.R.string.cancel) - .listItemsSingleChoice( - items = listOf( + var selected = 0 + activity!!.materialAlertDialog() + .setTitle(R.string.what_should_update) + .setNegativeButton(android.R.string.cancel, null) + .setSingleChoiceItems( + arrayOf( context.getString( R.string.top_category, presenter.allCategories.first().name @@ -615,14 +618,23 @@ class LibraryController( R.string.categories_in_global_update ) ), - selection = { _, index, _ -> - preferences.updateOnRefresh().set(index) - when (index) { - 0 -> updateLibrary(presenter.allCategories.first()) - else -> updateLibrary() - } + -1 + ) { dialog, index -> + selected = index + (dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = true + } + .setPositiveButton( + R.string.update + ) { _, _ -> + preferences.updateOnRefresh().set(selected) + when (selected) { + 0 -> updateLibrary(presenter.allCategories.first()) + else -> updateLibrary() } - ).positiveButton(R.string.update).show() + } + .show().apply { + getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = false + } } else -> { when (preferences.updateOnRefresh().getOrDefault()) { @@ -1583,7 +1595,7 @@ class LibraryController( } } - override fun sheetIsExpanded(): Boolean = false + override fun sheetIsFullscreen(): Boolean = false override fun handleSheetBack(): Boolean { if (binding.recyclerCover.isClickable) { @@ -1711,10 +1723,13 @@ class LibraryController( R.id.action_move_to_category -> showChangeMangaCategoriesSheet() R.id.action_share -> shareManga() R.id.action_delete -> { - MaterialDialog(activity!!).message(R.string.remove_from_library_question) - .positiveButton(R.string.remove) { + activity!!.materialAlertDialog() + .setMessage(R.string.remove_from_library_question) + .setPositiveButton(R.string.remove) { _, _ -> deleteMangasFromLibrary() - }.negativeButton(android.R.string.no).show() + } + .setNegativeButton(android.R.string.cancel, null) + .show() } R.id.action_download_unread -> { presenter.downloadUnread(selectedMangas.toList()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 15e89c827a..7d584497a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.image.coil.loadManga import eu.kanade.tachiyomi.databinding.MangaGridItemBinding import eu.kanade.tachiyomi.util.lang.highlightText +import eu.kanade.tachiyomi.util.system.dpToPx /** * Class used to hold the displayed data of a manga in the library, like the cover or the title. @@ -93,6 +94,20 @@ class LibraryGridHolder( } } + override fun toggleActivation() { + super.toggleActivation() + setSelected(adapter.isSelected(flexibleAdapterPosition)) + } + + fun setSelected(isSelected: Boolean) { + with(binding) { + card.strokeWidth = if (isSelected) 3.dpToPx else 1.dpToPx + arrayOf(card, unreadDownloadBadge.root, title, subtitle).forEach { + it.isSelected = isSelected + } + } + } + private fun setCover(manga: Manga) { if ((adapter.recyclerView.context as? Activity)?.isDestroyed == true) return binding.coverThumbnail.loadManga(manga) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 902d534619..deb0a30fcc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -44,7 +44,8 @@ abstract class LibraryHolder( item.manga.source == LocalSource.ID -> -2 else -> item.downloadCount }, - showTotal + showTotal, + this is LibraryGridHolder ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 3135a88271..1e6b353c4f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.MangaGridItemBinding import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.widget.AutofitRecyclerView import uy.kohesive.injekt.Injekt @@ -74,13 +75,17 @@ class LibraryItem( bottomMargin = 6.dpToPx } } else if (libraryLayout == 2) { - binding.constraintLayout.background = ContextCompat.getDrawable( - context, - R.drawable.library_confortable_grid_selector + binding.constraintLayout.background = context.contextCompatDrawable( + R.drawable.library_comfortable_grid_selector ) - binding.constraintLayout.foreground = ContextCompat.getDrawable( - context, - R.drawable.library_confortable_grid_selector_overlay + binding.constraintLayout.foreground = context.contextCompatDrawable( + R.drawable.library_comfortable_grid_selector_overlay + ) + binding.card.setCardForegroundColor( + ContextCompat.getColorStateList( + context, + R.color.library_comfortable_grid_foreground + ) ) } if (isFixedSize) { @@ -95,10 +100,10 @@ class LibraryItem( binding.coverThumbnail.adjustViewBounds = false binding.coverThumbnail.layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - (parent.itemWidth / 3f * 3.7f).toInt() + (parent.itemWidth / 3f * 3.875f).toInt() ) } else { - binding.constraintLayout.minHeight = coverHeight + binding.constraintLayout.minHeight = coverHeight / 2 binding.coverThumbnail.minimumHeight = (parent.itemWidth / 3f * 3.6f).toInt() binding.coverThumbnail.maxHeight = (parent.itemWidth / 3f * 6f).toInt() } @@ -123,6 +128,7 @@ class LibraryItem( payloads: MutableList? ) { holder.onSetValues(this) + (holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position)) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt index 9daf148603..ec7415cfbd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt @@ -5,9 +5,6 @@ import android.util.AttributeSet import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.utils.MDUtil.isLandscape import com.google.android.material.slider.Slider import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R @@ -18,6 +15,8 @@ import eu.kanade.tachiyomi.util.bindToPreference import eu.kanade.tachiyomi.util.lang.withSubtitle import eu.kanade.tachiyomi.util.system.bottomCutoutInset import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.isLandscape +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.topCutoutInset import eu.kanade.tachiyomi.util.view.checkHeightThen import eu.kanade.tachiyomi.util.view.numberOfRowsForValue @@ -56,10 +55,11 @@ class LibraryDisplayView @JvmOverloads constructor(context: Context, attrs: Attr recycler.adapter = adapter adapter.isHandleDragEnabled = true adapter.isLongPressDragEnabled = true - MaterialDialog(context).title(R.string.reorder_filters) - .customView(view = recycler, scrollable = false) - .negativeButton(android.R.string.cancel) - .positiveButton(R.string.reorder) { + context.materialAlertDialog() + .setTitle(R.string.reorder_filters) + .setView(recycler) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.reorder) { _, _ -> val order = adapter.currentItems.map { it.char }.joinToString("") preferences.filterOrder().set(order) recycler.adapter = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt index 34f1c76f3f..3cbc683ef1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.library.display import android.view.View import android.view.View.inflate -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.bluelinelabs.conductor.Controller import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.setting.SettingsLibraryController +import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.view.compatToolTipText import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog @@ -28,12 +28,7 @@ open class TabbedLibraryDisplaySheet(val controller: Controller) : displayView.mainView = controller.view binding.menu.isVisible = controller !is SettingsLibraryController binding.menu.compatToolTipText = context.getString(R.string.more_library_settings) - binding.menu.setImageDrawable( - ContextCompat.getDrawable( - context, - R.drawable.ic_outline_settings_24dp - ) - ) + binding.menu.setImageDrawable(context.contextCompatDrawable(R.drawable.ic_outline_settings_24dp)) binding.menu.setOnClickListener { controller.router.pushController(SettingsLibraryController().withFadeTransaction()) dismiss() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index 8aa972f012..079724ebef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -41,7 +41,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import kotlin.math.max -import kotlin.math.min import kotlin.math.roundToInt class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : @@ -96,8 +95,6 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri var libraryRecyler: View? = null var controller: LibraryController? = null var bottomBarHeight = 0 - val shadowAlpha = 0.15f - val shadow2Alpha = 0.05f override fun onFinishInflate() { super.onFinishInflate() @@ -117,15 +114,11 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri ?: controller.activityBinding?.root?.rootWindowInsets?.systemWindowInsetBottom ?: 0 } - val shadow2: View = controller.binding.shadow2 - val shadow: View = controller.binding.shadow sheetBehavior?.addBottomSheetCallback( object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, progress: Float) { this@FilterBottomSheet.controller?.updateFilterSheetY() binding.pill.alpha = (1 - max(0f, progress)) * 0.25f - shadow2.alpha = (1 - max(0f, progress)) * shadow2Alpha - shadow.alpha = (1 + min(0f, progress)) * shadowAlpha updateRootPadding(progress) } @@ -163,7 +156,6 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri else -> 0f } ) - shadow.alpha = if (sheetBehavior.isHidden()) 0f else shadowAlpha if (binding.secondLayout.width + (binding.groupBy.width * 2) + 20.dpToPx < width) { binding.secondLayout.removeView(binding.viewOptions) @@ -192,10 +184,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri } private fun stateChanged(state: Int) { - val shadow = controller?.binding?.shadow ?: return controller?.updateHopperY() if (state == BottomSheetBehavior.STATE_COLLAPSED) { - shadow.alpha = shadowAlpha libraryRecyler?.updatePaddingRelative(bottom = sheetBehavior?.peekHeight ?: 0 + 10.dpToPx + bottomBarHeight) } if (state == BottomSheetBehavior.STATE_EXPANDED) { @@ -204,7 +194,6 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri if (state == BottomSheetBehavior.STATE_HIDDEN) { onGroupClicked(ACTION_HIDE_FILTER_TIP) reSortViews() - shadow.alpha = 0f libraryRecyler?.updatePaddingRelative(bottom = 10.dpToPx + bottomBarHeight) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 1fe3b4fbe2..8c456d19e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -8,7 +8,7 @@ import android.content.Context import android.content.Intent import android.graphics.Color import android.graphics.Rect -import android.graphics.drawable.Drawable +import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Build import android.os.Bundle @@ -25,13 +25,12 @@ import android.view.WindowInsets import android.view.WindowManager import android.webkit.WebView import androidx.annotation.IdRes -import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.appcompat.widget.Toolbar -import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.net.toUri import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding @@ -101,15 +100,15 @@ import java.util.Date import java.util.concurrent.TimeUnit import kotlin.math.abs import kotlin.math.max +import kotlin.math.min open class MainActivity : BaseActivity(), DownloadServiceListener { protected lateinit var router: Router - var drawerArrow: DrawerArrowDrawable? = null - private set - private var searchDrawable: Drawable? = null - private var dismissDrawable: Drawable? = null + private val searchDrawable by lazy { contextCompatDrawable(R.drawable.ic_search_24dp) } + protected val backDrawable by lazy { contextCompatDrawable(R.drawable.ic_arrow_back_24dp) } + private val dismissDrawable by lazy { contextCompatDrawable(R.drawable.ic_close_24dp) } private var gestureDetector: GestureDetectorCompat? = null private var snackBar: Snackbar? = null @@ -128,6 +127,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi var tabAnimation: ValueAnimator? = null var overflowDialog: Dialog? = null var currentToolbar: Toolbar? = null + var ogWidth: Int = Int.MAX_VALUE fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) { this.snackBar = snackBar @@ -142,6 +142,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } override fun attachBaseContext(newBase: Context?) { + ogWidth = min(newBase?.resources?.configuration?.screenWidthDp ?: Int.MAX_VALUE, ogWidth) super.attachBaseContext(newBase?.prepareSideNavContext()) } @@ -169,17 +170,14 @@ open class MainActivity : BaseActivity(), DownloadServiceLi setContentView(binding.root) - drawerArrow = DrawerArrowDrawable(this) - drawerArrow?.color = getResourceColor(R.attr.actionBarTintColor) binding.toolbar.overflowIcon?.setTint(getResourceColor(R.attr.actionBarTintColor)) - searchDrawable = ContextCompat.getDrawable( - this, - R.drawable.ic_search_24dp - ) - dismissDrawable = ContextCompat.getDrawable( - this, - R.drawable.ic_close_24dp - ) + + val a = obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar, android.R.attr.windowLightStatusBar)) + val wic = WindowInsetsControllerCompat(window, window.decorView) + val isLight = a.getBoolean(0, false) + wic.isAppearanceLightStatusBars = isLight + wic.isAppearanceLightNavigationBars = isLight + a.recycle() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) @@ -261,6 +259,10 @@ open class MainActivity : BaseActivity(), DownloadServiceLi router = Conductor.attachRouter(this, container, savedInstanceState) + arrayOf(binding.toolbar, binding.cardToolbar).forEach { toolbar -> + toolbar.setNavigationIconTint(getResourceColor(R.attr.actionBarTintColor)) + toolbar.router = router + } if (router.hasRootController()) { nav.selectedItemId = when (router.backstack.firstOrNull()?.controller) { @@ -371,7 +373,8 @@ open class MainActivity : BaseActivity(), DownloadServiceLi syncActivityViewWithController(router.backstack.lastOrNull()?.controller) - binding.toolbar.navigationIcon = if (router.backstackSize > 1) drawerArrow else searchDrawable + val navIcon = if (router.backstackSize > 1) backDrawable else searchDrawable + binding.toolbar.navigationIcon = navIcon (router.backstack.lastOrNull()?.controller as? BaseController<*>)?.setTitle() (router.backstack.lastOrNull()?.controller as? SettingsController)?.setTitle() @@ -419,7 +422,8 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } binding.toolbar.isVisible = !show binding.cardFrame.isVisible = show - if (changeBG) { + val bgColor = (binding.appBar.background as? ColorDrawable)?.color ?: Color.TRANSPARENT + if (changeBG && (if (solidBG) bgColor == Color.TRANSPARENT else false)) { binding.appBar.setBackgroundColor( if (show && !solidBG) Color.TRANSPARENT else getResourceColor(R.attr.colorSurface) ) @@ -533,10 +537,11 @@ open class MainActivity : BaseActivity(), DownloadServiceLi .titleTextSize( 20 ) - .titleTextColor(android.R.color.white).descriptionTextSize(16) - .descriptionTextColor(R.color.md_white_1000_76) + .titleTextColorInt(getResourceColor(R.attr.colorOnSecondary)).descriptionTextSize(16) + .descriptionTextColorInt(getResourceColor(R.attr.colorOnSecondary)) .icon(contextCompatDrawable(R.drawable.ic_recent_read_32dp)) - .targetCircleColor(android.R.color.white).targetRadius(45), + .targetCircleColor(android.R.color.white) + .targetRadius(45), object : TapTargetView.Listener() { override fun onTargetClick(view: TapTargetView) { super.onTargetClick(view) @@ -839,15 +844,10 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } setFloatingToolbar(canShowFloatingToolbar(to)) val onRoot = router.backstackSize == 1 - if (onRoot) { - binding.toolbar.navigationIcon = searchDrawable - binding.cardToolbar.navigationIcon = searchDrawable - } else { - binding.toolbar.navigationIcon = drawerArrow - binding.cardToolbar.navigationIcon = drawerArrow - } + val navIcon = if (onRoot) searchDrawable else backDrawable + binding.toolbar.navigationIcon = navIcon + binding.cardToolbar.navigationIcon = navIcon binding.cardToolbar.subtitle = null - drawerArrow?.progress = 1f nav.visibility = if (!hideBottomNav) View.VISIBLE else nav.visibility if (nav == binding.sideNav) { @@ -1047,5 +1047,5 @@ interface BottomSheetController { fun hideSheet() fun toggleSheet() fun handleSheetBack(): Boolean - fun sheetIsExpanded(): Boolean + fun sheetIsFullscreen(): Boolean } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/OverflowDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/OverflowDialog.kt index 8dcdd78566..58ecd580b6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/OverflowDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/OverflowDialog.kt @@ -1,10 +1,11 @@ package eu.kanade.tachiyomi.ui.main import android.app.Dialog +import android.content.res.ColorStateList import android.graphics.Color -import android.os.Build -import android.view.View import android.view.ViewGroup +import androidx.core.graphics.ColorUtils +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updateLayoutParams import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import eu.kanade.tachiyomi.BuildConfig @@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.data.preference.toggle import eu.kanade.tachiyomi.databinding.TachiOverflowLayoutBinding import eu.kanade.tachiyomi.util.lang.withSubtitle import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.openInBrowser import uy.kohesive.injekt.injectLazy @@ -25,6 +27,13 @@ class OverflowDialog(activity: MainActivity) : Dialog(activity, R.style.Overflow init { setContentView(binding.root) + binding.overflowCardView.backgroundTintList = ColorStateList.valueOf( + ColorUtils.blendARGB( + activity.getResourceColor(R.attr.background), + activity.getResourceColor(R.attr.colorSecondary), + 0.075f + ) + ) binding.touchOutside.setOnClickListener { cancel() } @@ -82,12 +91,9 @@ class OverflowDialog(activity: MainActivity) : Dialog(activity, R.style.Overflow window?.let { window -> window.navigationBarColor = Color.TRANSPARENT window.decorView.fitsSystemWindows = true - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .rem(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .rem(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) - } + val wic = WindowInsetsControllerCompat(window, window.decorView) + wic.isAppearanceLightStatusBars = false + wic.isAppearanceLightNavigationBars = false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 064dd01792..5f1c130119 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -29,8 +29,8 @@ class SearchActivity : MainActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding.toolbar.navigationIcon = drawerArrow - binding.cardToolbar.navigationIcon = drawerArrow + binding.toolbar.navigationIcon = backDrawable + binding.cardToolbar.navigationIcon = backDrawable binding.toolbar.setNavigationOnClickListener { popToRoot() } binding.cardToolbar.setNavigationOnClickListener { popToRoot() } (router.backstack.lastOrNull()?.controller as? BaseController<*>)?.setTitle() @@ -74,9 +74,8 @@ class SearchActivity : MainActivity() { return } setFloatingToolbar(canShowFloatingToolbar(to)) - binding.cardToolbar.navigationIcon = drawerArrow - binding.toolbar.navigationIcon = drawerArrow - drawerArrow?.progress = 1f + binding.cardToolbar.navigationIcon = backDrawable + binding.toolbar.navigationIcon = backDrawable nav.isVisible = false binding.bottomView?.isVisible = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt index d594eed38e..98c5d33c5a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt @@ -15,9 +15,6 @@ import androidx.core.view.children import androidx.core.view.isVisible import coil.loadAny import coil.request.Parameters -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.customview.getCustomView import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import eu.kanade.tachiyomi.R @@ -33,6 +30,7 @@ import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.isInNightMode +import eu.kanade.tachiyomi.util.system.materialAlertDialog import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -66,18 +64,23 @@ class EditMangaDialog : DialogController { } override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val dialog = MaterialDialog(activity!!).apply { - customView(viewRes = R.layout.edit_manga_dialog, scrollable = true) - negativeButton(android.R.string.cancel) - positiveButton(R.string.save) { onPositiveButtonClick() } + binding = EditMangaDialogBinding.inflate(activity!!.layoutInflater) + val dialog = activity!!.materialAlertDialog().apply { + setView(binding.root) + setNegativeButton(android.R.string.cancel, null) + setPositiveButton(R.string.save) { _, _ -> onPositiveButtonClick() } } - binding = EditMangaDialogBinding.bind(dialog.getCustomView()) onViewCreated() - dialog.setOnShowListener { - val dView = (it as? MaterialDialog)?.view - dView?.contentLayout?.scrollView?.scrollTo(0, 0) + val updateScrollIndicators = { + binding.scrollIndicatorDown.isVisible = binding.scrollView.canScrollVertically(1) } - return dialog + binding.scrollView.setOnScrollChangeListener { _, _, _, _, _ -> + updateScrollIndicators() + } + binding.scrollView.post { + updateScrollIndicators() + } + return dialog.create() } fun onViewCreated() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt index 22080aa2e3..3e68e917f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/FullCoverDialog.kt @@ -12,6 +12,7 @@ import android.view.View import android.view.animation.DecelerateInterpolator import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.animation.addListener +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updateLayoutParams import androidx.transition.ChangeBounds import androidx.transition.ChangeImageTransform @@ -41,6 +42,7 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable ).toLong() init { + window?.setDimAmount(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 0.45f else 0.77f) setContentView(binding.root) binding.touchOutside.setOnClickListener { @@ -140,12 +142,9 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable window?.let { window -> window.navigationBarColor = Color.TRANSPARENT window.decorView.fitsSystemWindows = true - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .rem(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .rem(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) - } + val wic = WindowInsetsControllerCompat(window, window.decorView) + wic.isAppearanceLightStatusBars = false + wic.isAppearanceLightNavigationBars = false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt index 15082caa38..91144f6cbd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsAdapter.kt @@ -124,6 +124,7 @@ class MangaDetailsAdapter( interface MangaHeaderInterface { fun coverColor(): Int? + fun accentColor(): Int? fun mangaPresenter(): MangaDetailsPresenter fun prepareToShareManga() fun openInWebView() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index df3d8f089b..50dcc19605 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -18,11 +18,15 @@ import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.inputmethod.InputMethodManager +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePaddingRelative @@ -32,10 +36,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.imageLoader import coil.request.ImageRequest -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.checkbox.checkBoxPrompt -import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked -import com.afollestad.materialdialogs.utils.MDUtil.isLandscape import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.google.android.material.snackbar.BaseTransientBottomBar @@ -49,6 +49,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.data.image.coil.getBestColor import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.track.model.TrackSearch @@ -58,6 +59,7 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet +import eu.kanade.tachiyomi.ui.base.MiniSearchView import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder @@ -83,12 +85,20 @@ import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.moveCategories import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.ThemeUtil +import eu.kanade.tachiyomi.util.system.addCheckBoxPrompt +import eu.kanade.tachiyomi.util.system.contextCompatColor +import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getPrefTheme import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.isInNightMode +import eu.kanade.tachiyomi.util.system.isLandscape import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.isPromptChecked import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.materialAlertDialog +import eu.kanade.tachiyomi.util.system.setCustomTitleAndMessage import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets @@ -107,6 +117,7 @@ import java.io.File import java.io.IOException import java.util.Locale import kotlin.math.max +import kotlin.math.roundToInt class MangaDetailsController : BaseController, @@ -155,6 +166,8 @@ class MangaDetailsController : var colorAnimator: ValueAnimator? = null val presenter: MangaDetailsPresenter var coverColor: Int? = null + var accentColor: Int? = null + var headerColor: Int? = null var toolbarIsColored = false private var snack: Snackbar? = null val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) @@ -178,7 +191,7 @@ class MangaDetailsController : var fullCoverActive = false override fun getTitle(): String? { - return null + return manga?.title } override fun createBinding(inflater: LayoutInflater) = MangaDetailsControllerBinding.inflate(inflater) @@ -188,6 +201,8 @@ class MangaDetailsController : super.onViewCreated(view) coverColor = null fullCoverActive = false + setAccentColorValue() + setHeaderColorValue() setTabletMode(view) setRecycler(view) @@ -200,9 +215,79 @@ class MangaDetailsController : presenter.onCreate() binding.swipeRefresh.isRefreshing = presenter.isLoading binding.swipeRefresh.setOnRefreshListener { presenter.refreshAll() } + updateToolbarTitleAlpha() requestFilePermissionsSafe(301, presenter.preferences, presenter.manga.isLocal()) } + private fun setAccentColorValue(colorToUse: Int? = null) { + val context = view?.context ?: return + accentColor = if (presenter.preferences.themeMangaDetails()) { + (colorToUse ?: manga?.vibrantCoverColor)?.let { + val luminance = ColorUtils.calculateLuminance(it).toFloat() + if (if (!context.isInNightMode()) luminance > 0.4 else luminance <= 0.6) { + ColorUtils.blendARGB( + it, + context.contextCompatColor(R.color.colorOnDownloadBadgeDayNight), + (if (!context.isInNightMode()) luminance else -(luminance - 1)) + .toFloat() * if (context.isInNightMode()) 0.33f else 0.5f + ) + } else { + it + } + } + } else { + null + } + } + + private fun setRefreshStyle() { + with(binding.swipeRefresh) { + if (presenter.preferences.themeMangaDetails() && accentColor != null && headerColor != null) { + val newColor = makeColorFrom( + hueOf = accentColor!!, + satAndLumOf = context.getResourceColor(R.attr.actionBarTintColor) + ) + setColorSchemeColors(newColor) + setProgressBackgroundColorSchemeColor(headerColor!!) + } else { + setStyle() + } + } + } + + private fun setHeaderColorValue(colorToUse: Int? = null) { + val context = view?.context ?: return + headerColor = if (presenter.preferences.themeMangaDetails()) { + (colorToUse ?: manga?.vibrantCoverColor)?.let { color -> + val newColor = + makeColorFrom(color, context.getResourceColor(R.attr.colorPrimaryVariant)) + activity?.window?.navigationBarColor = ColorUtils.setAlphaComponent( + newColor, + Color.alpha(activity?.window?.navigationBarColor ?: Color.BLACK) + ) + newColor + } + } else { + null + } + setRefreshStyle() + } + + @ColorInt + private fun makeColorFrom(@ColorInt hueOf: Int, @ColorInt satAndLumOf: Int): Int { + val satLumArray = FloatArray(3) + val hueArray = FloatArray(3) + ColorUtils.colorToHSL(satAndLumOf, satLumArray) + ColorUtils.colorToHSL(hueOf, hueArray) + return ColorUtils.HSLToColor( + floatArrayOf( + hueArray[0], + satLumArray[1], + satLumArray[2] + ) + ) + } + /** Check if device is tablet, and use a second recycler to hold the details header if so */ private fun setTabletMode(view: View) { isTablet = view.context.isTablet() && view.context.isLandscape() @@ -222,6 +307,7 @@ class MangaDetailsController : presenter.onDestroy() adapter = null trackingBottomSheet = null + updateToolbarTitleAlpha(1f) super.onDestroyView(view) } @@ -241,7 +327,6 @@ class MangaDetailsController : val appbarHeight = array.getDimensionPixelSize(0, 0) array.recycle() val offset = 10.dpToPx - binding.swipeRefresh.setStyle() binding.swipeRefresh.setDistanceToTriggerSync(70.dpToPx) activityBinding!!.appBar.elevation = 0f @@ -271,6 +356,7 @@ class MangaDetailsController : override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (!isTablet) { + updateToolbarTitleAlpha(isScrollingDown = dy > 0) val atTop = !recyclerView.canScrollVertically(-1) val tY = getHeader()?.binding?.backdrop?.translationY ?: 0f getHeader()?.binding?.backdrop?.translationY = max(0f, tY + dy * 0.25f) @@ -280,6 +366,7 @@ class MangaDetailsController : override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { val atTop = !recyclerView.canScrollVertically(-1) + updateToolbarTitleAlpha() if (atTop) getHeader()?.binding?.backdrop?.translationY = 0f } } @@ -308,43 +395,49 @@ class MangaDetailsController : /** Set the toolbar to fully transparent or colored and translucent */ private fun colorToolbar(isColor: Boolean, animate: Boolean = true) { if (isColor == toolbarIsColored || (isTablet && isColor)) return + val activity = activity ?: return toolbarIsColored = isColor val isCurrentController = router?.backstack?.lastOrNull()?.controller == this@MangaDetailsController if (isCurrentController) setTitle() if (actionMode != null) { - activityBinding?.toolbar?.setBackgroundColor(Color.TRANSPARENT) return } - val color = - coverColor ?: activity!!.getResourceColor(R.attr.colorPrimaryVariant) - val colorFrom = - if (colorAnimator?.isRunning == true) activity?.window?.statusBarColor - ?: color - else ColorUtils.setAlphaComponent( - color, - if (toolbarIsColored) 0 else 175 - ) - val colorTo = ColorUtils.setAlphaComponent( - color, - if (toolbarIsColored) 175 else 0 - ) + val scrollingColor = headerColor ?: activity.getResourceColor(R.attr.colorPrimaryVariant) + if (ThemeUtil.hasDarkActionBarInLight(activity, activity.getPrefTheme(presenter.preferences))) { + setActionBar(!toolbarIsColored) + } + val topColor = ColorUtils.setAlphaComponent(scrollingColor, 0) + val scrollingStatusColor = ColorUtils.setAlphaComponent(scrollingColor, (0.87f * 255).roundToInt()) colorAnimator?.cancel() if (animate) { - colorAnimator = ValueAnimator.ofObject( - android.animation.ArgbEvaluator(), - colorFrom, - colorTo + colorAnimator = ValueAnimator.ofFloat( + if (toolbarIsColored) 0f else 1f, + if (toolbarIsColored) 1f else 0f ) colorAnimator?.duration = 250 // milliseconds colorAnimator?.addUpdateListener { animator -> - activityBinding?.toolbar?.setBackgroundColor(animator.animatedValue as Int) - activity?.window?.statusBarColor = (animator.animatedValue as Int) + activityBinding?.appBar?.setBackgroundColor( + ColorUtils.blendARGB( + topColor, + scrollingColor, + animator.animatedValue as Float + ) + ) + activity.window?.statusBarColor = if (toolbarIsColored) { + ColorUtils.blendARGB( + topColor, + scrollingStatusColor, + animator.animatedValue as Float + ) + } else { + Color.TRANSPARENT + } } colorAnimator?.start() } else { - activityBinding?.toolbar?.setBackgroundColor(colorTo) - activity?.window?.statusBarColor = colorTo + activityBinding?.appBar?.setBackgroundColor(if (toolbarIsColored) scrollingColor else topColor) + activity.window?.statusBarColor = if (toolbarIsColored) scrollingStatusColor else topColor } } @@ -360,20 +453,42 @@ class MangaDetailsController : if (bitmap != null) { Palette.from(bitmap).generate { if (it == null) return@generate - val colorBack = view.context.getResourceColor( - R.attr.background - ) + val colorBack = view.context.getResourceColor(R.attr.background) // this makes the color more consistent regardless of theme + val dominant = it.getDominantColor(colorBack) + val domLum = ColorUtils.calculateLuminance(dominant) + val lumWrongForTheme = (if (view.context.isInNightMode()) domLum > 0.8 else domLum <= 0.2) val backDropColor = - ColorUtils.blendARGB(it.getVibrantColor(colorBack), colorBack, .35f) + ColorUtils.blendARGB( + it.getDominantColor(colorBack), + colorBack, + if (lumWrongForTheme) 0.9f else 0.65f + ) coverColor = backDropColor - getHeader()?.setBackDrop(backDropColor) - if (toolbarIsColored) { - val translucentColor = - ColorUtils.setAlphaComponent(backDropColor, 175) - activityBinding?.toolbar?.setBackgroundColor(translucentColor) - activity?.window?.statusBarColor = translucentColor + if (presenter.preferences.themeMangaDetails()) { + launchUI { + view.context.getResourceColor(R.attr.colorSecondary) + val vibrantColor = it.getBestColor() ?: return@launchUI + manga?.vibrantCoverColor = vibrantColor + setAccentColorValue(vibrantColor) + setHeaderColorValue(vibrantColor) + getHeader()?.updateColors() + if (adapter?.itemCount ?: 0 > 1) { + (presenter.chapters).forEach { chapter -> + val chapterHolder = + binding.recycler.findViewHolderForItemId(chapter.id!!) as? ChapterHolder + ?: return@forEach + chapterHolder.notifyStatus( + chapter.status, + isLocked(), + chapter.progress + ) + } + } + } + } else { + getHeader()?.setBackDrop(backDropColor) } } } @@ -402,28 +517,42 @@ class MangaDetailsController : else R.attr.actionBarTintColor ) ?: Color.BLACK activityBinding.toolbar.setTitleTextColor(iconPrimary) - activity.drawerArrow?.color = iconPrimary + updateToolbarTitleAlpha() activityBinding.toolbar.overflowIcon?.setTint(iconPrimary) - activityBinding.mainContent.systemUiVisibility = if (forThis) { - activityBinding.mainContent.systemUiVisibility.or( - View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - ) - } else activityBinding.mainContent.systemUiVisibility.rem( - View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - ) + val wic = WindowInsetsControllerCompat(activity.window, activity.window.decorView) + wic.isAppearanceLightStatusBars = forThis + activityBinding.toolbar.setNavigationIconTint(iconPrimary) + activityBinding.toolbar.navigationIcon = + activity.contextCompatDrawable(R.drawable.ic_arrow_back_24dp) + val menu = activityBinding.toolbar.menu ?: return + menu.findItem(R.id.action_download)?.tintIcon(iconPrimary) + menu.findItem(R.id.action_search)?.tintIcon(iconPrimary) + menu.findItem(R.id.action_mark_all_as_read)?.tintIcon(iconPrimary) + val searchView = menu.findItem(R.id.action_search)?.actionView as? MiniSearchView ?: return + activityBinding.toolbar.collapseIcon = activity.contextCompatDrawable(R.drawable.ic_arrow_back_24dp)?.apply { + setTint(iconPrimary) + } + searchView.tintBar(iconPrimary) + } + } + + private fun MenuItem.tintIcon(color: Int) { + val drawable = icon + if (drawable != null) { + val wrapped = DrawableCompat.wrap(drawable) + drawable.mutate() + DrawableCompat.setTint(wrapped, color) + icon = drawable } } private fun setStatusBarAndToolbar() { - activity?.window?.statusBarColor = if (toolbarIsColored) { - val translucentColor = ColorUtils.setAlphaComponent(coverColor ?: Color.TRANSPARENT, 175) - activityBinding?.toolbar?.setBackgroundColor(translucentColor) - translucentColor - } else Color.TRANSPARENT - activityBinding?.appBar?.setBackgroundColor(Color.TRANSPARENT) - activityBinding?.toolbar?.setBackgroundColor( - activity?.window?.statusBarColor - ?: Color.TRANSPARENT + val topColor = Color.TRANSPARENT + val scrollingColor = headerColor ?: activity!!.getResourceColor(R.attr.colorPrimaryVariant) + val scrollingStatusColor = ColorUtils.setAlphaComponent(scrollingColor, (0.87f * 255).roundToInt()) + activity?.window?.statusBarColor = if (toolbarIsColored) scrollingStatusColor else topColor + activityBinding?.appBar?.setBackgroundColor( + if (toolbarIsColored) scrollingColor else topColor ) } @@ -454,6 +583,7 @@ class MangaDetailsController : super.onChangeStarted(handler, type) if (type.isEnter) { setActionBar(true) + updateToolbarTitleAlpha(0f) setStatusBarAndToolbar() } else { if (router.backstack.lastOrNull()?.controller is DialogController) { @@ -475,7 +605,6 @@ class MangaDetailsController : if (router.backstack.last().controller !is FloatingSearchInterface) { activityBinding?.appBar?.setBackgroundColor(colorSurface) } - activityBinding?.toolbar?.setBackgroundColor(colorSurface) activity?.window?.statusBarColor = activity?.getResourceColor( android.R.attr.statusBarColor ) ?: colorSurface @@ -520,19 +649,26 @@ class MangaDetailsController : } 1 -> return else -> { - MaterialDialog(context).title(R.string.chapters_removed).message( - text = context.resources.getQuantityString( - R.plurals.deleted_chapters, - deletedChapters.size, - deletedChapters.size, - deletedChapters.joinToString("\n") { it.name } + context.materialAlertDialog() + .setCustomTitleAndMessage( + R.string.chapters_removed, + context.resources.getQuantityString( + R.plurals.deleted_chapters, + deletedChapters.size, + deletedChapters.size, + deletedChapters.joinToString("\n") { it.name } + ) ) - ).positiveButton(R.string.delete) { - presenter.deleteChapters(deletedChapters, false) - if (it.isCheckPromptChecked()) deleteRemovedPref.set(2) - }.negativeButton(R.string.keep) { - if (it.isCheckPromptChecked()) deleteRemovedPref.set(1) - }.cancelOnTouchOutside(false).checkBoxPrompt(R.string.remember_this_choice) {}.show() + .setPositiveButton(R.string.delete) { dialog, _ -> + presenter.deleteChapters(deletedChapters, false) + if (dialog.isPromptChecked) deleteRemovedPref.set(2) + } + .setNegativeButton(R.string.keep) { dialog, _ -> + if (dialog.isPromptChecked) deleteRemovedPref.set(1) + } + .setCancelable(false) + .addCheckBoxPrompt(R.string.remember_this_choice) + .show() } } } @@ -793,12 +929,9 @@ class MangaDetailsController : //region action bar menu methods override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - colorToolbar(binding.recycler.canScrollVertically(-1)) - activityBinding?.toolbar?.navigationIcon = - activityBinding?.toolbar?.navigationIcon?.mutate()?.apply { - setTint(view?.context?.getResourceColor(R.attr.actionBarTintColor) ?: Color.WHITE) - } inflater.inflate(R.menu.manga_details, menu) + colorToolbar(binding.recycler.canScrollVertically(-1)) + setActionBar(toolbarIsColored) val editItem = menu.findItem(R.id.action_edit) editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch && @@ -818,15 +951,10 @@ class MangaDetailsController : R.string.migrate_, presenter.manga.seriesType(view!!.context) ) - val iconPrimary = view?.context?.getResourceColor(R.attr.colorOnBackground) - ?: Color.BLACK - menu.findItem(R.id.action_download).icon?.mutate()?.setTint(iconPrimary) - editItem.icon?.mutate()?.setTint(iconPrimary) val searchItem = menu.findItem(R.id.action_search) val searchView = searchItem.actionView as SearchView searchView.queryHint = resources?.getString(R.string.search_chapters) - searchItem.icon?.mutate()?.setTint(iconPrimary) searchItem.collapseActionView() if (query.isNotEmpty()) { searchItem.expandActionView() @@ -868,17 +996,23 @@ class MangaDetailsController : ) } R.id.action_mark_all_as_read -> { - MaterialDialog(view!!.context).message(R.string.mark_all_chapters_as_read) - .positiveButton(R.string.mark_as_read) { + activity!!.materialAlertDialog() + .setMessage(R.string.mark_all_chapters_as_read) + .setPositiveButton(R.string.mark_as_read) { _, _ -> markAsRead(presenter.chapters) - }.negativeButton(android.R.string.cancel).show() + } + .setNegativeButton(android.R.string.cancel, null) + .show() } R.id.remove_all, R.id.remove_read, R.id.remove_non_bookmarked -> massDeleteChapters(item.itemId) R.id.action_mark_all_as_unread -> { - MaterialDialog(view!!.context).message(R.string.mark_all_chapters_as_unread) - .positiveButton(R.string.mark_as_unread) { + activity!!.materialAlertDialog() + .setMessage(R.string.mark_all_chapters_as_unread) + .setPositiveButton(R.string.mark_as_unread) { _, _ -> markAsUnread(presenter.chapters) - }.negativeButton(android.R.string.cancel).show() + } + .setNegativeButton(android.R.string.cancel, null) + .show() } R.id.download_next, R.id.download_next_5, R.id.download_custom, R.id.download_unread, R.id.download_all -> downloadChapters( item.itemId @@ -980,17 +1114,49 @@ class MangaDetailsController : private fun massDeleteChapters(chapters: List, isEverything: Boolean) { val context = view?.context ?: return - MaterialDialog(context).message( - text = - if (isEverything) context.getString(R.string.remove_all_downloads) - else context.resources.getQuantityString( - R.plurals.remove_n_chapters, - chapters.size, - chapters.size + context.materialAlertDialog() + .setMessage( + if (isEverything) context.getString(R.string.remove_all_downloads) + else context.resources.getQuantityString( + R.plurals.remove_n_chapters, + chapters.size, + chapters.size + ) ) - ).positiveButton(R.string.remove) { - presenter.deleteChapters(chapters, isEverything = isEverything) - }.negativeButton(android.R.string.cancel).show() + .setPositiveButton(R.string.remove) { _, _ -> + presenter.deleteChapters(chapters, isEverything = isEverything) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + private fun updateToolbarTitleAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float? = null, isScrollingDown: Boolean = false) { + if (( + router?.backstack?.lastOrNull()?.controller != this@MangaDetailsController && + alpha == null + ) || isScrollingDown + ) return + val scrolledList = binding.recycler + val toolbarTextView = activityBinding?.toolbar?.toolbarTitle ?: return + toolbarTextView.setTextColor( + ColorUtils.setAlphaComponent( + toolbarTextView.currentTextColor, + ( + when { + // Specific alpha provided + alpha != null -> alpha + + // First item isn't in view, full opacity + ((scrolledList.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0) -> 1f + ((scrolledList.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) -> 0f + + // Based on scroll amount when first item is in view + else -> (scrolledList.computeVerticalScrollOffset() - (20.dpToPx)) + .coerceIn(0, 255) / 255f + } * 255 + ).roundToInt() + ) + ) } private fun downloadChapters(choice: Int) { @@ -1021,6 +1187,7 @@ class MangaDetailsController : //region Interface methods override fun coverColor(): Int? = coverColor + override fun accentColor(): Int? = accentColor override fun topCoverHeight(): Int = headerHeight override fun startDownloadNow(position: Int) { @@ -1030,6 +1197,7 @@ class MangaDetailsController : // In case the recycler is at the bottom and collapsing the header makes it unscrollable override fun updateScroll() { + updateToolbarTitleAlpha() if (!binding.recycler.canScrollVertically(-1)) { getHeader()?.binding?.backdrop?.translationY = 0f activityBinding?.appBar?.y = 0f @@ -1304,7 +1472,6 @@ class MangaDetailsController : private fun createActionModeIfNeeded() { if (actionMode == null) { actionMode = (activity as AppCompatActivity).startSupportActionMode(this) - activityBinding?.toolbar?.setBackgroundColor(Color.TRANSPARENT) val view = activity?.window?.currentFocus ?: return val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager ?: return @@ -1396,11 +1563,7 @@ class MangaDetailsController : val drawable = binding.mangaCoverFull.drawable ?: return fullCoverActive = true drawable.alpha = 255 - val fullCoverDialog = FullCoverDialog( - this, - drawable, - thumbView - ) + val fullCoverDialog = FullCoverDialog(this, drawable, thumbView) fullCoverDialog.setOnDismissListener { fullCoverActive = false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index 2328317fdc..99094990c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -38,8 +38,8 @@ import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem -import eu.kanade.tachiyomi.ui.manga.track.SetTrackReadingDatesDialog import eu.kanade.tachiyomi.ui.manga.track.TrackItem +import eu.kanade.tachiyomi.ui.manga.track.TrackingBottomSheet import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterSort @@ -998,11 +998,11 @@ class MangaDetailsPresenter( updateRemote(track, item.service) } - fun getSuggestedDate(readingDate: SetTrackReadingDatesDialog.ReadingDate): Long? { + fun getSuggestedDate(readingDate: TrackingBottomSheet.ReadingDate): Long? { val chapters = db.getHistoryByMangaId(manga.id ?: 0L).executeAsBlocking() val date = when (readingDate) { - SetTrackReadingDatesDialog.ReadingDate.Start -> chapters.minOfOrNull { it.last_read } - SetTrackReadingDatesDialog.ReadingDate.Finish -> chapters.maxOfOrNull { it.last_read } + TrackingBottomSheet.ReadingDate.Start -> chapters.minOfOrNull { it.last_read } + TrackingBottomSheet.ReadingDate.Finish -> chapters.maxOfOrNull { it.last_read } } ?: return null return if (date <= 0L) null else date } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index 67c1b94bd1..78cd8b86e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -21,6 +21,7 @@ import androidx.core.text.scale import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.core.widget.TextViewCompat import androidx.transition.TransitionSet import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import coil.request.CachePolicy @@ -423,6 +424,9 @@ class MangaHeaderHolder( if (!manga.initialized) return updateCover(manga) + if (adapter.preferences.themeMangaDetails()) { + updateColors(false) + } } private fun setGenreTags(binding: MangaHeaderItemBinding, manga: Manga) { @@ -435,11 +439,11 @@ class MangaHeaderHolder( val accentArray = FloatArray(3) ColorUtils.colorToHSL(baseTagColor, bgArray) - ColorUtils.colorToHSL(context.getResourceColor(R.attr.colorSecondary), accentArray) + ColorUtils.colorToHSL(adapter.delegate.accentColor() ?: context.getResourceColor(R.attr.colorSecondary), accentArray) val downloadedColor = ColorUtils.setAlphaComponent( ColorUtils.HSLToColor( floatArrayOf( - bgArray[0], + if (adapter.delegate.accentColor() != null) accentArray[0] else bgArray[0], bgArray[1], ( when { @@ -491,7 +495,7 @@ class MangaHeaderHolder( stateListAnimator = AnimatorInflater.loadStateListAnimator(context, R.animator.icon_btn_state_list_anim) backgroundTintList = ColorStateList.valueOf( ColorUtils.blendARGB( - context.getResourceColor(R.attr.colorSecondary), + adapter.delegate.accentColor() ?: context.getResourceColor(R.attr.colorSecondary), context.getResourceColor(R.attr.background), 0.706f ) @@ -518,6 +522,50 @@ class MangaHeaderHolder( binding.trueBackdrop.setBackgroundColor(color) } + fun updateColors(updateAll: Boolean = true) { + binding ?: return + val accentColor = adapter.delegate.accentColor() ?: return + val manga = adapter.presenter.manga + with(binding) { + trueBackdrop.setBackgroundColor( + adapter.delegate.coverColor() + ?: trueBackdrop.context.getResourceColor(R.attr.background) + ) + TextViewCompat.setCompoundDrawableTintList(moreButton, ColorStateList.valueOf(accentColor)) + moreButton.setTextColor(accentColor) + TextViewCompat.setCompoundDrawableTintList(lessButton, ColorStateList.valueOf(accentColor)) + lessButton.setTextColor(accentColor) + shareButton.imageTintList = ColorStateList.valueOf(accentColor) + webviewButton.imageTintList = ColorStateList.valueOf(accentColor) + filterButton.imageTintList = ColorStateList.valueOf(accentColor) + + val states = arrayOf( + intArrayOf(-android.R.attr.state_enabled), + intArrayOf() + ) + + val colors = intArrayOf( + ColorUtils.setAlphaComponent(root.context.getResourceColor(R.attr.tabBarIconInactive), 43), + accentColor + ) + + startReadingButton.backgroundTintList = ColorStateList(states, colors) + + val textColors = intArrayOf( + ColorUtils.setAlphaComponent(root.context.getResourceColor(R.attr.colorOnSurface), 97), + root.context.getResourceColor(android.R.attr.textColorPrimaryInverse) + ) + startReadingButton.setTextColor(ColorStateList(states, textColors)) + trackButton.iconTint = ColorStateList.valueOf(accentColor) + favoriteButton.iconTint = ColorStateList.valueOf(accentColor) + if (updateAll) { + trackButton.checked(trackButton.stateListAnimator != null) + favoriteButton.checked(favoriteButton.stateListAnimator != null) + setGenreTags(this, manga) + } + } + } + fun updateTracking() { binding ?: return val presenter = adapter.delegate.mangaPresenter() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt index 5a6fb1fdfb..c26e0a6a99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt @@ -26,14 +26,14 @@ class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: Att private fun checkedFilter(checkBox: TriStateCheckBox, state: TriStateCheckBox.State) { if (state != TriStateCheckBox.State.UNCHECKED) { if (binding.showAll == checkBox && state == TriStateCheckBox.State.CHECKED) { - binding.showUnread.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) - binding.showDownload.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) - binding.showBookmark.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) + binding.showUnread.setState(TriStateCheckBox.State.UNCHECKED, true) + binding.showDownload.setState(TriStateCheckBox.State.UNCHECKED) + binding.showBookmark.setState(TriStateCheckBox.State.UNCHECKED) } else { if (binding.showAll == checkBox) { binding.showAll.state = TriStateCheckBox.State.CHECKED } else { - binding.showAll.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) + binding.showAll.setState(TriStateCheckBox.State.UNCHECKED, true) } } } else if ( @@ -41,7 +41,7 @@ class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: Att binding.showDownload.isUnchecked && binding.showBookmark.isUnchecked ) { - binding.showAll.animateDrawableToState(TriStateCheckBox.State.CHECKED) + binding.showAll.setState(TriStateCheckBox.State.CHECKED, true) } mOnCheckedChangeListener?.onCheckedChanged(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt index 973937a371..7e96d9baf8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt @@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.animation.AnimatorSet import android.animation.ObjectAnimator +import android.content.res.ColorStateList import android.view.View import androidx.core.view.isVisible +import androidx.core.widget.TextViewCompat import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download @@ -12,6 +14,7 @@ import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.ui.manga.MangaDetailsAdapter import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.widget.EndAnimatorListener import eu.kanade.tachiyomi.widget.StartAnimatorListener @@ -147,6 +150,17 @@ class ChapterHolder( } fun notifyStatus(status: Download.State, locked: Boolean, progress: Int, animated: Boolean = false) = with(binding.downloadButton.downloadButton) { + adapter.delegate.accentColor()?.let { + binding.startView.backgroundTintList = ColorStateList.valueOf(it) + binding.bookmark.imageTintList = ColorStateList.valueOf( + context.getResourceColor(android.R.attr.textColorPrimaryInverse) + ) + TextViewCompat.setCompoundDrawableTintList( + binding.chapterTitle, + ColorStateList.valueOf(it) + ) + colorSecondary = it + } if (locked) { isVisible = false return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt index f8dbf73117..92f69bad27 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt @@ -5,8 +5,6 @@ import android.view.LayoutInflater import android.view.View import androidx.core.view.isInvisible import androidx.core.view.isVisible -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.google.android.material.bottomsheet.BottomSheetBehavior import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga @@ -14,6 +12,8 @@ import eu.kanade.tachiyomi.databinding.ChapterSortBottomSheetBinding import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.materialAlertDialog +import eu.kanade.tachiyomi.util.system.setNegativeStateItems import eu.kanade.tachiyomi.util.view.setBottomEdge import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog import eu.kanade.tachiyomi.widget.SortTextView @@ -158,27 +158,28 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : binding.filterGroupsButton.setOnClickListener { val scanlators = presenter.allChapterScanlators.toList() val filteredScanlators = - presenter.manga.filtered_scanlators?.let { ChapterUtil.getScanlators(it) } - ?: scanlators.toSet() - val preselected = if (scanlators.size == filteredScanlators.size) { - IntArray(0) - } else { - filteredScanlators.map { scanlators.indexOf(it) }.toIntArray() - } - MaterialDialog(activity!!) - .title(R.string.filter_groups) - .listItemsMultiChoice( - items = scanlators, - initialSelection = preselected, - allowEmptySelection = true, - ) { _, selections, _ -> - val selected = selections.map { scanlators[it] }.toSet() - presenter.setScanlatorFilter(selected) + ( + presenter.manga.filtered_scanlators?.let { ChapterUtil.getScanlators(it) } + ?.toMutableSet() + ?: mutableSetOf() + ) + val preselected = scanlators.map { it in filteredScanlators }.toBooleanArray() + activity.materialAlertDialog() + .setTitle(R.string.filter_groups) + .setNegativeStateItems(scanlators, preselected) { _, pos, checked -> + if (checked) { + filteredScanlators.add(scanlators[pos]) + } else { + filteredScanlators.remove(scanlators[pos]) + } } - .negativeButton(R.string.reset) { - presenter.setScanlatorFilter(presenter.allChapterScanlators) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.filter) { _, _ -> + presenter.setScanlatorFilter(filteredScanlators) + } + .setNeutralButton(R.string.reset) { _, _, -> + presenter.setScanlatorFilter(emptySet()) } - .positiveButton(R.string.filter) .show() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt deleted file mode 100644 index ce21d2ebce..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt +++ /dev/null @@ -1,75 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga.track - -import android.app.Dialog -import android.os.Bundle -import android.widget.NumberPicker -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.customview.getCustomView -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class SetTrackChaptersDialog : DialogController - where T : SetTrackChaptersDialog.Listener { - - private val item: TrackItem - private lateinit var listener: Listener - - constructor(target: T, item: TrackItem) : super( - Bundle().apply { - putSerializable(KEY_ITEM_TRACK, item.track) - } - ) { - listener = target - this.item = item - } - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) { - val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track - val service = Injekt.get().getService(track.sync_id)!! - item = TrackItem(track, service) - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val item = item - - val dialog = MaterialDialog(activity!!) - .title(R.string.chapters) - .customView(viewRes = R.layout.track_chapters_dialog) - .negativeButton(android.R.string.cancel) - .positiveButton(android.R.string.ok) { dialog -> - val view = dialog.getCustomView() - // Remove focus to update selected number - val np: NumberPicker = view.findViewById(R.id.chapters_picker) - np.clearFocus() - listener.setChaptersRead(item, np.value) - } - - val view = dialog.getCustomView() - val np: NumberPicker = view.findViewById(R.id.chapters_picker) - // Set initial value - np.value = item.track?.last_chapter_read ?: 0 - if (item.track?.total_chapters ?: 0 > 0) { - np.wrapSelectorWheel = true - np.maxValue = item.track?.total_chapters ?: 0 - } else { - // Don't allow to go from 0 to 9999 - np.wrapSelectorWheel = false - } - - return dialog - } - - interface Listener { - fun setChaptersRead(item: TrackItem, chaptersRead: Int) - } - - private companion object { - const val KEY_ITEM_TRACK = "SetTrackChaptersDialog.item.track" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt deleted file mode 100644 index 8701e972a7..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt +++ /dev/null @@ -1,152 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga.track - -import android.app.Dialog -import android.os.Bundle -import androidx.core.os.bundleOf -import com.afollestad.date.dayOfMonth -import com.afollestad.date.month -import com.afollestad.date.year -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.datetime.datePicker -import com.bluelinelabs.conductor.Controller -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.util.view.setDate -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.text.DateFormat -import java.util.Calendar - -class SetTrackReadingDatesDialog : DialogController - where T : Controller { - - private val item: TrackItem - - private val dateToUpdate: ReadingDate - private val suggestedDate: Long? - - private val preferences: PreferencesHelper by injectLazy() - private val dateFormat: DateFormat by lazy { - preferences.dateFormat() - } - - private lateinit var listener: Listener - - constructor(target: T, listener: Listener, dateToUpdate: ReadingDate, item: TrackItem, suggestedDate: Long?) : super( - bundleOf(KEY_ITEM_TRACK to item.track) - ) { - targetController = target - this.listener = listener - this.item = item - this.dateToUpdate = dateToUpdate - this.suggestedDate = suggestedDate - } - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) { - val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track - val service = Injekt.get().getService(track.sync_id)!! - item = TrackItem(track, service) - dateToUpdate = ReadingDate.Start - suggestedDate = null - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialDialog(activity!!) - .title( - when (dateToUpdate) { - ReadingDate.Start -> R.string.started_reading_date - ReadingDate.Finish -> R.string.finished_reading_date - } - ) - .datePicker(currentDate = getCurrentDate()) { _, date -> - listener.setReadingDate(item, dateToUpdate, date.timeInMillis) - } - .neutralButton(R.string.remove) { - listener.setReadingDate(item, dateToUpdate, -1L) - }.apply { - getSuggestedDate()?.let { - message( - text = it, - applySettings = { - messageTextView.setOnClickListener { - this@apply.setDate(suggestedDate ?: 0L) - } - } - ) - } - } - } - - private fun getSuggestedDate(): String? { - item.track ?: return null - val date = when (dateToUpdate) { - ReadingDate.Start -> item.track.started_reading_date - ReadingDate.Finish -> item.track.finished_reading_date - } - if (date != 0L) { - if (suggestedDate != null) { - val calendar = Calendar.getInstance() - calendar.timeInMillis = date - val suggestedCalendar = Calendar.getInstance() - suggestedCalendar.timeInMillis = suggestedDate - return if (date > suggestedDate && - ( - suggestedCalendar.year != calendar.year || - suggestedCalendar.month != calendar.month || - suggestedCalendar.dayOfMonth != calendar.dayOfMonth - ) - ) { - activity?.getString( - R.string.suggested_date_, - dateFormat.format(suggestedDate) - ) - } else { - null - } - } - } - suggestedDate?.let { - return activity?.getString( - R.string.suggested_date_, - dateFormat.format(suggestedDate) - ) - } - return null - } - - private fun getCurrentDate(): Calendar { - // Today if no date is set, otherwise the already set date - return Calendar.getInstance().apply { - suggestedDate?.let { - timeInMillis = it - } - item.track?.let { - val date = when (dateToUpdate) { - ReadingDate.Start -> it.started_reading_date - ReadingDate.Finish -> it.finished_reading_date - } - if (date != 0L) { - timeInMillis = date - } - } - } - } - - interface Listener { - fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long) - } - - enum class ReadingDate { - Start, - Finish - } - - companion object { - private const val KEY_ITEM_TRACK = "SetTrackReadingDatesDialog.item.track" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt deleted file mode 100644 index 0f9a13b514..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt +++ /dev/null @@ -1,74 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga.track - -import android.app.Dialog -import android.os.Bundle -import android.widget.NumberPicker -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.customview.getCustomView -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class SetTrackScoreDialog : DialogController where T : SetTrackScoreDialog.Listener { - - private val item: TrackItem - private lateinit var listener: Listener - - constructor(target: T, item: TrackItem) : super( - Bundle().apply { - putSerializable(KEY_ITEM_TRACK, item.track) - } - ) { - listener = target - this.item = item - } - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) { - val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track - val service = Injekt.get().getService(track.sync_id)!! - item = TrackItem(track, service) - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val item = item - - val dialog = MaterialDialog(activity!!).title(R.string.score) - .customView(R.layout.track_score_dialog) - .negativeButton(android.R.string.cancel).positiveButton(android.R.string.ok) { dialog -> - val view = dialog.getCustomView() - // Remove focus to update selected number - val np: NumberPicker = view.findViewById(R.id.score_picker) - np.clearFocus() - - listener.setScore(item, np.value) - } - - val view = dialog.getCustomView() - val np: NumberPicker = view.findViewById(R.id.score_picker) - val scores = item.service.getScoreList().toTypedArray() - np.maxValue = scores.size - 1 - np.displayedValues = scores - - // Set initial value - val displayedScore = item.service.displayScore(item.track!!) - if (displayedScore != "-") { - val index = scores.indexOf(displayedScore) - np.value = if (index != -1) index else 0 - } - - return dialog - } - - interface Listener { - fun setScore(item: TrackItem, score: Int) - } - - private companion object { - const val KEY_ITEM_TRACK = "SetTrackScoreDialog.item.track" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt deleted file mode 100644 index ee9fbc7a90..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.ui.manga.track - -import android.app.Dialog -import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.ui.base.controller.DialogController -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class SetTrackStatusDialog : DialogController - where T : SetTrackStatusDialog.Listener { - - private val item: TrackItem - private lateinit var listener: Listener - - constructor(target: T, item: TrackItem) : super( - Bundle().apply { - putSerializable(KEY_ITEM_TRACK, item.track) - } - ) { - listener = target - this.item = item - } - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) { - val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track - val service = Injekt.get().getService(track.sync_id)!! - item = TrackItem(track, service) - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val item = item - val statusList = item.service.getStatusList() - val statusString = statusList.map { item.service.getStatus(it) } - val selectedIndex = statusList.indexOf(item.track?.status) - - return MaterialDialog(activity!!) - .title(R.string.status) - .negativeButton(android.R.string.cancel) - .listItemsSingleChoice( - items = statusString, - initialSelection = selectedIndex, - waitForPositiveButton = false - ) { dialog, position, _ -> - listener.setStatus(item, position) - dialog.dismiss() - } - } - - interface Listener { - fun setStatus(item: TrackItem, selection: Int) - } - - private companion object { - const val KEY_ITEM_TRACK = "SetTrackStatusDialog.item.track" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt index 4cb107e58d..dd8ae8a84d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.manga.track +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import eu.kanade.tachiyomi.R @@ -47,7 +48,7 @@ class TrackAdapter(controller: OnClickListener) : RecyclerView.Adapter : DialogController - where T : TrackRemoveDialog.Listener { - - private val item: TrackItem - private lateinit var listener: Listener - - constructor(target: T, item: TrackItem) : super( - Bundle().apply { - putSerializable(KEY_ITEM_TRACK, item.track) - } - ) { - listener = target - this.item = item - } - - @Suppress("unused") - constructor(bundle: Bundle) : super(bundle) { - val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track - val service = Injekt.get().getService(track.sync_id)!! - item = TrackItem(track, service) - } - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val item = item - - val dialog = MaterialDialog(activity!!).title(R.string.remove_tracking) - .negativeButton(android.R.string.cancel) - - if (item.service.canRemoveFromService()) { - val serviceName = activity!!.getString(item.service.nameRes()) - dialog.checkBoxPrompt( - text = activity!!.getString( - R.string.remove_tracking_from_, - serviceName - ), - isCheckedDefault = true, - onToggle = null - ).positiveButton(R.string.remove) { - listener.removeTracker( - item, - it.isCheckPromptChecked() - ) - } - dialog.getCheckBoxPrompt().textSize = 16f - } else { - dialog.positiveButton(R.string.remove) { listener.removeTracker(item, false) } - } - - return dialog - } - - interface Listener { - fun removeTracker(item: TrackItem, fromServiceAlso: Boolean) - } - - private companion object { - const val KEY_ITEM_TRACK = "TrackRemoveDialog.item.track" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt index a162bb7f64..2b7b98d486 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt @@ -7,12 +7,15 @@ import android.os.Bundle import android.text.Spannable import android.text.SpannableString import android.text.style.StyleSpan +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.PopupMenu import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.net.toUri import androidx.core.view.isVisible @@ -20,9 +23,8 @@ import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionManager -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItems import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.datepicker.MaterialDatePicker import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.listeners.addClickListener @@ -30,13 +32,21 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.UnattendedTrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.databinding.TrackChaptersDialogBinding +import eu.kanade.tachiyomi.databinding.TrackScoreDialogBinding import eu.kanade.tachiyomi.databinding.TrackingBottomSheetBinding import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsDivider import eu.kanade.tachiyomi.util.lang.indexesOf +import eu.kanade.tachiyomi.util.system.addCheckBoxPrompt import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.isPromptChecked import eu.kanade.tachiyomi.util.system.launchIO +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.openInBrowser +import eu.kanade.tachiyomi.util.system.toLocalCalendar +import eu.kanade.tachiyomi.util.system.toUtcCalendar import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener @@ -44,15 +54,12 @@ import eu.kanade.tachiyomi.util.view.checkHeightThen import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog import timber.log.Timber +import java.text.DateFormat +import java.util.Calendar class TrackingBottomSheet(private val controller: MangaDetailsController) : E2EBottomSheetDialog(controller.activity!!), - TrackAdapter.OnClickListener, - SetTrackStatusDialog.Listener, - SetTrackChaptersDialog.Listener, - SetTrackScoreDialog.Listener, - TrackRemoveDialog.Listener, - SetTrackReadingDatesDialog.Listener { + TrackAdapter.OnClickListener { val activity = controller.activity!! @@ -62,6 +69,11 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : private var adapter: TrackAdapter? = null private val searchItemAdapter = ItemAdapter() private val searchAdapter = FastAdapter.with(searchItemAdapter) + private var suggestedStartDate: Long? = null + private var suggestedFinishDate: Long? = null + private val dateFormat: DateFormat by lazy { + presenter.preferences.dateFormat() + } override fun createBinding(inflater: LayoutInflater) = TrackingBottomSheetBinding.inflate(inflater) @@ -125,6 +137,11 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : fullHeight - (insets?.systemWindowInsetTop ?: 0) - 30.dpToPx } } + + controller.viewScope.launchIO { + suggestedStartDate = presenter.getSuggestedDate(ReadingDate.Start) + suggestedFinishDate = presenter.getSuggestedDate(ReadingDate.Finish) + } } override fun onStart() { @@ -350,9 +367,9 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : searchingItem.service.nameRes() ) ) - MaterialDialog(activity) - .title(R.string.remove_previous_tracker) - .listItems(items = listOf(wordToSpan, text2), waitForPositiveButton = false) { dialog, i, _ -> + activity.materialAlertDialog() + .setTitle(R.string.remove_previous_tracker) + .setItems(arrayOf(wordToSpan, text2)) { dialog, i -> if (i == 0) { removeTracker(searchingItem, true) } @@ -361,7 +378,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : hideSearchView() dialog.dismiss() } - .negativeButton(android.R.string.cancel) + .setNegativeButton(android.R.string.cancel, null) .show() return } @@ -387,19 +404,58 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : return } - SetTrackStatusDialog(this, item).showDialog(controller.router) + val statusList = item.service.getStatusList() + val statusString = statusList.map { item.service.getStatus(it) } + val selectedIndex = statusList.indexOf(item.track.status) + + activity.materialAlertDialog() + .setTitle(R.string.status) + .setNegativeButton(android.R.string.cancel, null) + .setSingleChoiceItems( + statusString.toTypedArray(), + selectedIndex + ) { dialog, itemPosition -> + setStatus(item, itemPosition) + dialog.dismiss() + } + .show() } override fun onRemoveClick(position: Int) { val item = adapter?.getItem(position) ?: return if (item.track == null) return - if (controller.isNotOnline()) { - dismiss() - return - } + val dialog = activity.materialAlertDialog() + .setTitle(R.string.remove_tracking) + .setNegativeButton(android.R.string.cancel, null) - TrackRemoveDialog(this, item).showDialog(controller.router) + if (item.service.canRemoveFromService()) { + val serviceName = activity.getString(item.service.nameRes()) + if (!activity.isOnline()) { + dialog.setMessage( + activity.getString( + R.string.cannot_remove_tracking_while_offline, + serviceName + ) + ) + .setPositiveButton(R.string.remove) { _, _ -> + removeTracker(item, false) + } + } else { + dialog.addCheckBoxPrompt( + activity.getString(R.string.remove_tracking_from_, serviceName), + true + ) + .setPositiveButton(R.string.remove) { dialogI, _ -> + removeTracker(item, dialogI.isPromptChecked) + } + } + } else { + dialog.setPositiveButton(R.string.remove) { _, _ -> + removeTracker(item, false) + } + } + dialog.show() } override fun onChaptersClick(position: Int) { @@ -409,7 +465,30 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : dismiss() return } - SetTrackChaptersDialog(this, item).showDialog(controller.router) + + val binding = TrackChaptersDialogBinding.inflate(activity.layoutInflater) + val dialog = activity.materialAlertDialog() + .setTitle(R.string.chapters) + .setView(binding.root) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + // Remove focus to update selected number + val np = binding.chaptersPicker + np.clearFocus() + setChaptersRead(item, np.value) + } + + val np = binding.chaptersPicker + // Set initial value + np.value = item.track.last_chapter_read + if (item.track.total_chapters > 0) { + np.wrapSelectorWheel = true + np.maxValue = item.track.total_chapters + } else { + // Don't allow to go from 0 to 9999 + np.wrapSelectorWheel = false + } + dialog.show() } override fun onScoreClick(position: Int) { @@ -420,40 +499,153 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : return } - SetTrackScoreDialog(this, item).showDialog(controller.router) + val binding = TrackScoreDialogBinding.inflate(activity.layoutInflater) + val dialog = activity.materialAlertDialog() + .setTitle(R.string.score) + .setView(binding.root) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + val np = binding.scorePicker + np.clearFocus() + + setScore(item, np.value) + } + + val np = binding.scorePicker + val scores = item.service.getScoreList().toTypedArray() + np.maxValue = scores.size - 1 + np.displayedValues = scores + + // Set initial value + val displayedScore = item.service.displayScore(item.track) + if (displayedScore != "-") { + val index = scores.indexOf(displayedScore) + np.value = if (index != -1) index else 0 + } + + dialog.show() } - override fun onStartDateClick(position: Int) { + override fun onStartDateClick(view: View, position: Int) { val item = adapter?.getItem(position) ?: return if (item.track == null) return - val suggestedDate = presenter.getSuggestedDate(SetTrackReadingDatesDialog.ReadingDate.Start) - SetTrackReadingDatesDialog( - controller, - this, - SetTrackReadingDatesDialog.ReadingDate.Start, - item, - suggestedDate - ) - .showDialog(controller.router) + showMenuPicker(view, item, ReadingDate.Start, suggestedStartDate) } - override fun onFinishDateClick(position: Int) { + override fun onFinishDateClick(view: View, position: Int) { val item = adapter?.getItem(position) ?: return if (item.track == null) return - val suggestedDate = presenter.getSuggestedDate(SetTrackReadingDatesDialog.ReadingDate.Finish) - SetTrackReadingDatesDialog( - controller, - this, - SetTrackReadingDatesDialog.ReadingDate.Finish, - item, - suggestedDate - ) - .showDialog(controller.router) + showMenuPicker(view, item, ReadingDate.Finish, suggestedFinishDate) } - override fun setStatus(item: TrackItem, selection: Int) { + private fun showMenuPicker(view: View, trackItem: TrackItem, readingDate: ReadingDate, suggestedDate: Long?) { + val date = if (readingDate == ReadingDate.Start) { + trackItem.track?.started_reading_date + } else { + trackItem.track?.finished_reading_date + } ?: 0L + if (date <= 0L) { + showDatePicker(trackItem, readingDate, suggestedDate) + return + } + val popup = PopupMenu(activity, view, Gravity.NO_GRAVITY) + popup.menu.add(0, 0, 0, R.string.edit) + getSuggestedDate(trackItem, readingDate, suggestedDate)?.let { + val subMenu = popup.menu.addSubMenu(0, 1, 0, R.string.use_suggested_date) + subMenu.add(0, 2, 0, it) + } + popup.menu.add(0, 3, 0, R.string.remove) + + popup.setOnMenuItemClickListener { + when (it.itemId) { + 0 -> showDatePicker(trackItem, readingDate, suggestedDate) + 2 -> setReadingDate(trackItem, readingDate, suggestedDate!!) + 3 -> setReadingDate(trackItem, readingDate, -1L) + } + true + } + + popup.show() + } + + enum class ReadingDate { + Start, + Finish + } + + private fun showDatePicker(trackItem: TrackItem, readingDate: ReadingDate, suggestedDate: Long?) { + val dialog = MaterialDatePicker.Builder.datePicker() + .setTitleText( + when (readingDate) { + ReadingDate.Start -> R.string.started_reading_date + ReadingDate.Finish -> R.string.finished_reading_date + } + ) + .setSelection(getCurrentDate(trackItem, readingDate, suggestedDate)?.timeInMillis).apply { + } + .build() + + dialog.addOnPositiveButtonClickListener { utcMillis -> + val result = utcMillis.toLocalCalendar()?.timeInMillis + if (result != null) { + setReadingDate(trackItem, readingDate, result) + } + } + dialog.show((activity as AppCompatActivity).supportFragmentManager, readingDate.toString()) + } + + private fun getSuggestedDate(trackItem: TrackItem, readingDate: ReadingDate, suggestedDate: Long?): String? { + trackItem.track ?: return null + val date = when (readingDate) { + ReadingDate.Start -> trackItem.track.started_reading_date + ReadingDate.Finish -> trackItem.track.finished_reading_date + } + if (date != 0L) { + if (suggestedDate != null) { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + val suggestedCalendar = Calendar.getInstance() + suggestedCalendar.timeInMillis = suggestedDate + return if (date > suggestedDate && + ( + suggestedCalendar.get(Calendar.YEAR) != calendar.get(Calendar.YEAR) || + suggestedCalendar.get(Calendar.MONTH) != calendar.get(Calendar.MONTH) || + suggestedCalendar.get(Calendar.DAY_OF_MONTH) != calendar.get(Calendar.DAY_OF_MONTH) + ) + ) { + dateFormat.format(suggestedDate) + } else { + null + } + } + } + suggestedDate?.let { + return dateFormat.format(suggestedDate) + } + return null + } + + private fun getCurrentDate(trackItem: TrackItem, readingDate: ReadingDate, suggestedDate: Long?): Calendar? { + // Today if no date is set, otherwise the already set date + return Calendar.getInstance().apply { + suggestedDate?.let { + timeInMillis = it + } + trackItem.track?.let { + val date = when (readingDate) { + ReadingDate.Start -> it.started_reading_date + ReadingDate.Finish -> it.finished_reading_date + } + if (date != 0L) { + timeInMillis = date + } + } + }.timeInMillis.toUtcCalendar() + } + + fun setStatus(item: TrackItem, selection: Int) { presenter.setStatus(item, selection) refreshItem(item) } @@ -474,25 +666,26 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : } } - override fun setScore(item: TrackItem, score: Int) { + fun setScore(item: TrackItem, score: Int) { presenter.setScore(item, score) refreshItem(item) } - override fun setChaptersRead(item: TrackItem, chaptersRead: Int) { + private fun setChaptersRead(item: TrackItem, chaptersRead: Int) { presenter.setLastChapterRead(item, chaptersRead) refreshItem(item) } - override fun removeTracker(item: TrackItem, fromServiceAlso: Boolean) { + private fun removeTracker(item: TrackItem, fromServiceAlso: Boolean) { refreshTrack(item.service) presenter.removeTracker(item, fromServiceAlso) } - override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) { + private fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long) { + refreshTrack(item.service) when (type) { - SetTrackReadingDatesDialog.ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date) - SetTrackReadingDatesDialog.ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date) + ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date) + ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt index 70b9244bbb..e3d68f45d3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt @@ -40,12 +40,6 @@ class MigrationController : } override fun createBinding(inflater: LayoutInflater) = MigrationControllerBinding.inflate(inflater) - fun searchController(manga: Manga): SearchController { - val controller = SearchController(manga) - controller.targetController = this - - return controller - } override fun onViewCreated(view: View) { super.onViewCreated(view) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt index cddcb917fe..030dca3d7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.migration import android.app.Dialog import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.Controller import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController +import eu.kanade.tachiyomi.util.system.materialAlertDialog class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller { @@ -14,6 +14,7 @@ class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) var copy = false var mangaSet = 0 var mangaSkipped = 0 + constructor(target: T, copy: Boolean, mangaSet: Int, mangaSkipped: Int) : this() { targetController = target this.copy = copy @@ -28,17 +29,20 @@ class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) mangaSet, mangaSet, ( - if (mangaSkipped > 0) " " + view?.context?.getString(R.string.skipping_, mangaSkipped) + if (mangaSkipped > 0) " " + view?.context?.getString( + R.string.skipping_, + mangaSkipped + ) else "" ) ) ?: "" - return MaterialDialog(activity!!).show { - message(text = confirmString) - positiveButton(if (copy) R.string.copy_value else R.string.migrate) { + return activity!!.materialAlertDialog() + .setMessage(confirmString) + .setPositiveButton(if (copy) R.string.copy_value else R.string.migrate) { _, _ -> if (copy) (targetController as? MigrationListController)?.copyMangas() else (targetController as? MigrationListController)?.migrateMangas() } - negativeButton(android.R.string.no) - } + .setNegativeButton(android.R.string.cancel, null) + .create() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt index c817473faa..e5b5a2a1fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt @@ -1,29 +1,22 @@ package eu.kanade.tachiyomi.ui.migration -import android.app.Dialog import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import androidx.appcompat.widget.SearchView import androidx.core.os.bundleOf -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.main.BottomNavBarInterface import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardAdapter import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter -import eu.kanade.tachiyomi.util.view.withFadeTransaction import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -40,10 +33,6 @@ class SearchController( ), BottomNavBarInterface { - private var progress = 1 - var totalProgress = 0 - var newManga: Manga? = null - /** * Called when controller is initialized. */ @@ -63,66 +52,10 @@ class SearchController( bundle.getLongArray(SOURCES) ?: LongArray(0) ) - override fun getTitle(): String? { - if (totalProgress > 1) { - return "($progress/$totalProgress) ${super.getTitle()}" - } else { - return super.getTitle() - } - } - override fun createPresenter(): GlobalSearchPresenter { return SearchPresenter(initialQuery, manga!!, sources = sources) } - /*override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - if (totalProgress > 1) { - val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga) - menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable - .baseline_skip_next_white_24, null) - menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - 1 -> { - newManga = manga - migrateManga() - } - } - return true - }*/ - - fun migrateManga() { - val target = targetController as? MigrationInterface ?: return - val manga = manga ?: return - val newManga = newManga ?: return - - val nextManga = target.migrateManga(manga, newManga, true) - replaceWithNewSearchController(nextManga) - } - - fun copyManga() { - val target = targetController as? MigrationInterface ?: return - val manga = manga ?: return - val newManga = newManga ?: return - - val nextManga = target.migrateManga(manga, newManga, false) - replaceWithNewSearchController(nextManga) - } - - private fun replaceWithNewSearchController(manga: Manga?) { - if (manga != null) { - // router.popCurrentController() - val searchController = SearchController(manga) - searchController.targetController = targetController - searchController.progress = progress + 1 - searchController.totalProgress = totalProgress - router.replaceTopController(searchController.withFadeTransaction()) - } else router.popController(this) - } - override fun onMangaClick(manga: Manga) { if (targetController is MigrationListController) { val migrationListController = targetController as? MigrationListController @@ -132,10 +65,6 @@ class SearchController( router.popCurrentController() return } - newManga = manga - val dialog = MigrationDialog() - dialog.targetController = this - dialog.showDialog(router) } override fun onMangaLongClick(position: Int, adapter: GlobalSearchCardAdapter) { @@ -144,34 +73,6 @@ class SearchController( super.onMangaClick(manga) } - class MigrationDialog : DialogController() { - - private val preferences: PreferencesHelper by injectLazy() - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val prefValue = preferences.migrateFlags().getOrDefault() - - val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue) - - return MaterialDialog(activity!!) - .message(R.string.data_to_include_in_migration) - .listItemsMultiChoice( - items = MigrationFlags.titles.map - { resources?.getString(it) as CharSequence }, - initialSelection = preselected.toIntArray() - ) { _, positions, _ -> - val newValue = MigrationFlags.getFlagsFromPositions(positions.toTypedArray()) - preferences.migrateFlags().set(newValue) - } - .positiveButton(R.string.migrate) { - (targetController as? SearchController)?.migrateManga() - } - .negativeButton(R.string.copy_value) { - (targetController as? SearchController)?.copyManga() - } - } - } - /** * Adds items to the options menu. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt index 42fa37258d..83a0c82604 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt @@ -12,7 +12,6 @@ import android.widget.Toast import androidx.core.graphics.ColorUtils import androidx.recyclerview.widget.LinearLayoutManager import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat -import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -38,6 +37,7 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.liftAppbarWith @@ -428,23 +428,16 @@ class MigrationListController(bundle: Bundle? = null) : } override fun handleBack(): Boolean { - activity?.let { - MaterialDialog(it).show { - title(R.string.stop_migrating) - positiveButton(R.string.stop) { - router.popCurrentController() - migrationsJob?.cancel() - } - negativeButton(android.R.string.cancel) + activity?.materialAlertDialog() + ?.setTitle(R.string.stop_migrating) + ?.setPositiveButton(R.string.stop) { _, _ -> + router.popCurrentController() + migrationsJob?.cancel() } - } + ?.setNegativeButton(android.R.string.cancel, null)?.show() return true } - override fun onDestroyView(view: View) { - super.onDestroyView(view) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.migration_list, menu) } @@ -498,16 +491,13 @@ class MigrationListController(bundle: Bundle? = null) : override fun canChangeTabs(block: () -> Unit): Boolean { if (migrationsJob?.isCancelled == false || adapter?.allMangasDone() == true) { - activity?.let { - MaterialDialog(it).show { - title(R.string.stop_migrating) - positiveButton(R.string.stop) { - block() - migrationsJob?.cancel() - } - negativeButton(android.R.string.cancel) + activity?.materialAlertDialog() + ?.setTitle(R.string.stop_migrating) + ?.setPositiveButton(R.string.stop) { _, _ -> + block() + migrationsJob?.cancel() } - } + ?.setNegativeButton(android.R.string.cancel, null) return false } return true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 888b561f65..793d0982d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -27,13 +27,17 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.statusBars +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePaddingRelative import androidx.lifecycle.Lifecycle import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat -import com.afollestad.materialdialogs.MaterialDialog import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -79,16 +83,19 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.hasColoredActionBar import eu.kanade.tachiyomi.util.system.hasSideNavBar import eu.kanade.tachiyomi.util.system.isBottomTappable +import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.openInBrowser +import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat import eu.kanade.tachiyomi.util.system.spToPx import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.compatToolTipText -import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets +import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat import eu.kanade.tachiyomi.util.view.hide import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isExpanded @@ -104,7 +111,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.zhanghai.android.systemuihelper.SystemUiHelper import nucleus.factory.RequiresPresenter import timber.log.Timber import uy.kohesive.injekt.injectLazy @@ -113,6 +119,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale import kotlin.math.abs +import kotlin.math.max import kotlin.math.roundToInt /** @@ -120,9 +127,7 @@ import kotlin.math.roundToInt * viewers, to which calls from the presenter or UI events are delegated. */ @RequiresPresenter(ReaderPresenter::class) -class ReaderActivity : - BaseRxActivity(), - SystemUiHelper.OnVisibilityChangeListener { +class ReaderActivity : BaseRxActivity() { lateinit var binding: ReaderActivityBinding @@ -157,11 +162,6 @@ class ReaderActivity : private var fromUrl = false - /** - * System UI helper to hide status & navigation bar on all different API levels. - */ - private var systemUi: SystemUiHelper? = null - /** * Configuration at reader level, like background color or forced orientation. */ @@ -174,7 +174,8 @@ class ReaderActivity : var sheetManageNavColor = false - private var lightStatusBar = false + private val wic by lazy { WindowInsetsControllerCompat(window, binding.root) } + var lastVis = false private var snackbar: Snackbar? = null @@ -211,23 +212,18 @@ class ReaderActivity : binding = ReaderActivityBinding.inflate(layoutInflater) setContentView(binding.root) val a = obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar)) - lightStatusBar = a.getBoolean(0, false) + val lightStatusBar = a.getBoolean(0, false) a.recycle() setNotchCutoutMode() - var systemUiFlag = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - systemUiFlag = systemUiFlag.or(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) - } + wic.isAppearanceLightStatusBars = lightStatusBar + wic.isAppearanceLightNavigationBars = lightStatusBar + binding.appBar.setBackgroundColor(contextCompatColor(R.color.surface_alpha)) ViewCompat.setBackgroundTintList( binding.readerNav.root, ColorStateList.valueOf(contextCompatColor(R.color.surface_alpha)) ) - binding.readerLayout.systemUiVisibility = when (lightStatusBar) { - true -> binding.readerLayout.systemUiVisibility.or(systemUiFlag) - false -> binding.readerLayout.systemUiVisibility.rem(systemUiFlag) - } if (presenter.needsInit()) { fromUrl = handleIntentAction(intent) @@ -759,46 +755,91 @@ class ReaderActivity : if (!menuVisible) binding.chaptersSheet.chaptersBottomSheet.sheetBehavior?.hide() binding.chaptersSheet.root.sheetBehavior?.isGestureInsetBottomIgnored = true val peek = 50.dpToPx - binding.readerLayout.doOnApplyWindowInsets { _, insets, _ -> - sheetManageNavColor = when { - insets.isBottomTappable() -> { - window.navigationBarColor = Color.TRANSPARENT - false - } - insets.hasSideNavBar() -> { - window.navigationBarColor = getResourceColor(R.attr.colorSurface) - false - } - // if in portrait with 2/3 button mode, translucent nav bar - else -> { - true + binding.readerLayout.doOnApplyWindowInsetsCompat { _, insets, _ -> + setNavColor(insets) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (lastVis != insets.isVisible(statusBars()) && preferences.fullscreen().get()) { + onVisibilityChange(insets.isVisible(statusBars())) } + lastVis = insets.isVisible(statusBars()) } + if (!preferences.fullscreen().get() && sheetManageNavColor) { + window.navigationBarColor = getResourceColor(R.attr.colorSurface) + } binding.appBar.updateLayoutParams { - leftMargin = insets.systemWindowInsetLeft - rightMargin = insets.systemWindowInsetRight + leftMargin = insets.getInsetsIgnoringVisibility(systemBars()).left + rightMargin = insets.getInsetsIgnoringVisibility(systemBars()).right } binding.toolbar.updateLayoutParams { - topMargin = insets.systemWindowInsetTop + topMargin = insets.getInsetsIgnoringVisibility(systemBars()).top } binding.chaptersSheet.chaptersBottomSheet.updateLayoutParams { - leftMargin = insets.systemWindowInsetLeft - rightMargin = insets.systemWindowInsetRight - height = 280.dpToPx + insets.systemWindowInsetBottom + leftMargin = insets.getInsetsIgnoringVisibility(systemBars()).left + rightMargin = insets.getInsetsIgnoringVisibility(systemBars()).right + height = 280.dpToPx + insets.getInsetsIgnoringVisibility(systemBars()).bottom } binding.navLayout.updateLayoutParams { - leftMargin = 12.dpToPx + insets.systemWindowInsetLeft - rightMargin = 12.dpToPx + insets.systemWindowInsetRight + leftMargin = 12.dpToPx + insets.getInsetsIgnoringVisibility(systemBars()).left + rightMargin = 12.dpToPx + insets.getInsetsIgnoringVisibility(systemBars()).right } binding.chaptersSheet.root.sheetBehavior?.peekHeight = - peek + insets.getBottomGestureInsets() - binding.chaptersSheet.chapterRecycler.updatePaddingRelative(bottom = insets.systemWindowInsetBottom) + peek + if (preferences.fullscreen().get()) { + insets.getBottomGestureInsets() + } else { + val rootInsets = binding.root.rootWindowInsetsCompat ?: insets + max( + 0, + (rootInsets.getBottomGestureInsets()) - + rootInsets.getInsetsIgnoringVisibility(systemBars()).bottom + ) + } + binding.chaptersSheet.chapterRecycler.updatePaddingRelative(bottom = insets.getInsetsIgnoringVisibility(systemBars()).bottom) binding.viewerContainer.requestLayout() } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + @Suppress("DEPRECATION") + binding.readerLayout.setOnSystemUiVisibilityChangeListener { + if (preferences.fullscreen().get()) { + onVisibilityChange((it and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) + } + } + } } - fun showPageLayoutMenu() { + fun setNavColor(insets: WindowInsetsCompat) { + sheetManageNavColor = when { + Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1 -> { + // basically if in landscape on a phone + // For lollipop, draw opaque nav bar + window.navigationBarColor = when { + insets.hasSideNavBar() -> Color.BLACK + isInNightMode() -> { + ColorUtils.setAlphaComponent( + getResourceColor(R.attr.colorPrimaryVariant), + 179 + ) + } + else -> Color.argb(179, 0, 0, 0) + } + !insets.hasSideNavBar() + } + insets.isBottomTappable() -> { + window.navigationBarColor = Color.TRANSPARENT + false + } + insets.hasSideNavBar() -> { + window.navigationBarColor = getResourceColor(R.attr.colorSurface) + false + } + // if in portrait with 2/3 button mode, translucent nav bar + else -> { + true + } + } + } + + private fun showPageLayoutMenu() { with(binding.chaptersSheet.doublePage) { val config = (viewer as? PagerViewer)?.config val selectedId = when { @@ -843,7 +884,7 @@ class ReaderActivity : binding.viewerContainer.requestLayout() if (visible) { snackbar?.dismiss() - systemUi?.show() + wic.show(systemBars()) binding.readerMenu.isVisible = true if (binding.chaptersSheet.chaptersBottomSheet.sheetBehavior.isExpanded()) { @@ -867,7 +908,9 @@ class ReaderActivity : binding.chaptersSheet.chaptersBottomSheet.sheetBehavior?.collapse() } } else { - systemUi?.hide() + if (preferences.fullscreen().get()) { + wic.hide(systemBars()) + } if (animate) { val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) @@ -915,7 +958,7 @@ class ReaderActivity : ReadingModeType.WEBTOON.flagValue -> R.string.webtoon_style else -> R.string.left_to_right_viewer } - ).toLowerCase(Locale.getDefault()) + ).lowercase(Locale.getDefault()) ), 4000 ) { @@ -1276,10 +1319,13 @@ class ReaderActivity : private fun showSetCoverPrompt(page: ReaderPage) { if (page.status != Page.READY) return - MaterialDialog(this).title(R.string.use_image_as_cover) - .positiveButton(android.R.string.yes) { + materialAlertDialog() + .setMessage(R.string.use_image_as_cover) + .setPositiveButton(android.R.string.ok) { _, _ -> setAsCover(page) - }.negativeButton(android.R.string.no).show() + } + .setNegativeButton(android.R.string.cancel, null) + .show() } /** @@ -1371,19 +1417,30 @@ class ReaderActivity : ) } - override fun onVisibilityChange(visible: Boolean) { + private fun onVisibilityChange(visible: Boolean) { if (visible && !menuStickyVisible && !menuVisible && !binding.readerMenu.isVisible) { menuStickyVisible = visible if (visible) { coroutine = launchUI { delay(2000) - if (systemUi?.isShowing == true) { + if (window.decorView.rootWindowInsetsCompat?.isVisible(statusBars()) == true) { menuStickyVisible = false setMenuVisibility(false) } } - if (sheetManageNavColor) window.navigationBarColor = - getResourceColor(R.attr.colorSurface) + if (sheetManageNavColor) { + window.navigationBarColor = + ColorUtils.setAlphaComponent( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 || isInNightMode()) { + getResourceColor(R.attr.colorSurface) + } else Color.BLACK, + if (binding.root.rootWindowInsetsCompat?.hasSideNavBar() == true) { + 255 + } else { + 179 + } + ) + } binding.readerMenu.isVisible = true val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) toolbarAnimation.setAnimationListener( @@ -1559,14 +1616,9 @@ class ReaderActivity : * Sets the fullscreen reading mode (immersive) according to [enabled]. */ private fun setFullscreen(enabled: Boolean) { - systemUi = if (enabled) { - val level = SystemUiHelper.LEVEL_IMMERSIVE - val flags = SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES - - SystemUiHelper(this@ReaderActivity, level, flags, this@ReaderActivity) - } else { - null - } + WindowCompat.setDecorFitsSystemWindows(window, !enabled) + wic.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE + binding.root.rootWindowInsetsCompat?.let { setNavColor(it) } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterSheet.kt index 96547a9715..a77ab91fb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/chapter/ReaderChapterSheet.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.chapter import android.content.Context import android.content.res.ColorStateList import android.graphics.Color +import android.os.Build import android.util.AttributeSet import android.view.View import android.widget.LinearLayout @@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderPresenter import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.expand @@ -54,6 +56,13 @@ class ReaderChapterSheet @JvmOverloads constructor(context: Context, attrs: Attr val primary = ColorUtils.setAlphaComponent(fullPrimary, 200) + val hasLightNav = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 || activity.isInNightMode() + val navPrimary = ColorUtils.setAlphaComponent( + if (hasLightNav) { + fullPrimary + } else Color.BLACK, + 200 + ) sheetBehavior = BottomSheetBehavior.from(this) binding.chaptersButton.setOnClickListener { if (sheetBehavior.isExpanded()) { @@ -84,7 +93,7 @@ class ReaderChapterSheet @JvmOverloads constructor(context: Context, attrs: Attr binding.chapterRecycler.alpha = trueProgress if (activity.sheetManageNavColor && progress > 0f) { activity.window.navigationBarColor = - lerpColor(ColorUtils.setAlphaComponent(primary, 0), primary, trueProgress) + lerpColor(ColorUtils.setAlphaComponent(navPrimary, if (hasLightNav) 0 else 179), navPrimary, trueProgress) } } @@ -112,7 +121,7 @@ class ReaderChapterSheet @JvmOverloads constructor(context: Context, attrs: Attr } activity.binding.readerNav.root.alpha = 0f binding.chapterRecycler.alpha = 1f - if (activity.sheetManageNavColor) activity.window.navigationBarColor = primary + if (activity.sheetManageNavColor) activity.window.navigationBarColor = navPrimary } if (state == BottomSheetBehavior.STATE_HIDDEN) { activity.binding.readerNav.root.alpha = 0f diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/settings/ReaderFilterView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/settings/ReaderFilterView.kt index 4b6ee8d39b..38a1bcffc2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/settings/ReaderFilterView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/settings/ReaderFilterView.kt @@ -6,12 +6,10 @@ import android.os.Build import android.util.AttributeSet import android.view.Window import android.view.WindowManager -import android.widget.SeekBar import androidx.annotation.ColorInt import eu.kanade.tachiyomi.databinding.ReaderColorFilterBinding import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.widget.BaseReaderSettingsView -import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample @@ -46,14 +44,14 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib val argb = setValues(color) // Set brightness value - binding.txtBrightnessSeekbarValue.text = brightness.toString() - binding.brightnessSeekbar.progress = brightness + binding.txtBrightnessSliderValue.text = brightness.toString() + binding.brightnessSlider.value = brightness.toFloat() - // Initialize seekBar progress - binding.seekbarColorFilterAlpha.progress = argb[0] - binding.seekbarColorFilterRed.progress = argb[1] - binding.seekbarColorFilterGreen.progress = argb[2] - binding.seekbarColorFilterBlue.progress = argb[3] + // Initialize slider values + binding.sliderColorFilterAlpha.value = argb[0].toFloat() + binding.sliderColorFilterRed.value = argb[1].toFloat() + binding.sliderColorFilterGreen.value = argb[2].toFloat() + binding.sliderColorFilterBlue.value = argb[3].toFloat() // Set listeners binding.switchColorFilter.isChecked = preferences.colorFilter().get() @@ -67,79 +65,62 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib } binding.colorFilterMode.bindToPreference(preferences.colorFilterMode()) - binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener( - object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - binding.seekbarColorFilterRed.isEnabled = value > 0 && binding.seekbarColorFilterAlpha.isEnabled - binding.seekbarColorFilterGreen.isEnabled = value > 0 && binding.seekbarColorFilterAlpha.isEnabled - binding.seekbarColorFilterBlue.isEnabled = value > 0 && binding.seekbarColorFilterAlpha.isEnabled - if (fromUser) { - setColorValue(value, ALPHA_MASK, 24) - } - } + binding.sliderColorFilterAlpha.addOnChangeListener { _, value, fromUser -> + binding.sliderColorFilterRed.isEnabled = + value > 0 && binding.sliderColorFilterAlpha.isEnabled + binding.sliderColorFilterGreen.isEnabled = + value > 0 && binding.sliderColorFilterAlpha.isEnabled + binding.sliderColorFilterBlue.isEnabled = + value > 0 && binding.sliderColorFilterAlpha.isEnabled + if (fromUser) { + setColorValue(value.toInt(), ALPHA_MASK, 24) } - ) + } - setColorFilterSeekBar(binding.switchColorFilter.isChecked) + setColorFilterSlider(binding.switchColorFilter.isChecked) - binding.seekbarColorFilterRed.setOnSeekBarChangeListener( - object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, RED_MASK, 16) - } - } + binding.sliderColorFilterRed.addOnChangeListener { _, value, fromUser -> + if (fromUser) { + setColorValue(value.toInt(), RED_MASK, 16) } - ) + } - binding.seekbarColorFilterGreen.setOnSeekBarChangeListener( - object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, GREEN_MASK, 8) - } - } + binding.sliderColorFilterGreen.addOnChangeListener { _, value, fromUser -> + if (fromUser) { + setColorValue(value.toInt(), GREEN_MASK, 8) } - ) + } - binding.seekbarColorFilterBlue.setOnSeekBarChangeListener( - object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, BLUE_MASK, 0) - } - } + binding.sliderColorFilterBlue.addOnChangeListener { _, value, fromUser -> + if (fromUser) { + setColorValue(value.toInt(), BLUE_MASK, 0) } - ) + } - binding.brightnessSeekbar.setOnSeekBarChangeListener( - object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - preferences.customBrightnessValue().set(value) - } - } + binding.brightnessSlider.addOnChangeListener { _, value, fromUser -> + if (fromUser) { + preferences.customBrightnessValue().set(value.toInt()) } - ) + } } /** - * Set enabled status of seekBars belonging to color filter - * @param enabled determines if seekBar gets enabled + * Set enabled status of sliders belonging to color filter + * @param enabled determines if the sliders get enabled */ - private fun setColorFilterSeekBar(enabled: Boolean) { - binding.seekbarColorFilterRed.isEnabled = binding.seekbarColorFilterAlpha.progress > 0 && enabled - binding.seekbarColorFilterGreen.isEnabled = binding.seekbarColorFilterAlpha.progress > 0 && enabled - binding.seekbarColorFilterBlue.isEnabled = binding.seekbarColorFilterAlpha.progress > 0 && enabled - binding.seekbarColorFilterAlpha.isEnabled = enabled + private fun setColorFilterSlider(enabled: Boolean) { + binding.sliderColorFilterRed.isEnabled = binding.sliderColorFilterAlpha.value > 0 && enabled + binding.sliderColorFilterGreen.isEnabled = binding.sliderColorFilterAlpha.value > 0 && enabled + binding.sliderColorFilterBlue.isEnabled = binding.sliderColorFilterAlpha.value > 0 && enabled + binding.sliderColorFilterAlpha.isEnabled = enabled } /** - * Set enabled status of seekBars belonging to custom brightness - * @param enabled value which determines if seekBar gets enabled + * Set enabled status of sliders belonging to custom brightness + * @param enabled value which determines if slider gets enabled */ - private fun setCustomBrightnessSeekBar(enabled: Boolean) { - binding.brightnessSeekbar.isEnabled = enabled + private fun setCustomBrightnessSlider(enabled: Boolean) { + binding.brightnessSlider.isEnabled = enabled } /** @@ -174,7 +155,7 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib } else { setCustomBrightnessValue(0, true) } - setCustomBrightnessSeekBar(enabled) + setCustomBrightnessSlider(enabled) } fun setWindowBrightness() { @@ -190,7 +171,7 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib private fun setCustomBrightnessValue(value: Int, isDisabled: Boolean = false) { // Set black overlay visibility. if (!isDisabled) { - binding.txtBrightnessSeekbarValue.text = value.toString() + binding.txtBrightnessSliderValue.text = value.toString() window?.attributes = window?.attributes?.apply { screenBrightness = max(0.01f, value / 100f) } } else { window?.attributes = window?.attributes?.apply { screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } @@ -209,7 +190,7 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib .onEach { setColorFilterValue(it) } .launchIn(activity.scope) } - setColorFilterSeekBar(enabled) + setColorFilterSlider(enabled) } /** @@ -276,7 +257,7 @@ class ReaderFilterView @JvmOverloads constructor(context: Context, attrs: Attrib override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (Build.VERSION.SDK_INT >= 29 && changed) { - with(binding.brightnessSeekbar) { + with(binding.brightnessSlider) { boundingBox.set(this.left, this.top, this.right, this.bottom) this.systemGestureExclusionRects = exclusions } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt index 8ebbc38096..8af322dded 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt @@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF @@ -43,11 +44,20 @@ class ReaderProgressBar @JvmOverloads constructor( /** * The paint to use to draw the progress bar. */ - private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = context.getResourceColor(R.attr.colorSecondary) - isAntiAlias = true - strokeCap = Paint.Cap.ROUND - style = Paint.Style.STROKE + private var paint = setPaint() + + override fun setForegroundTintList(tint: ColorStateList?) { + super.setForegroundTintList(tint) + paint = setPaint() + } + + private fun setPaint(): Paint { + return Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = foregroundTintList?.defaultColor ?: context.getResourceColor(R.attr.colorSecondary) + isAntiAlias = true + strokeCap = Paint.Cap.ROUND + style = Paint.Style.STROKE + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index af4f1f07b9..90539393dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.content.res.ColorStateList import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.PointF @@ -38,6 +39,7 @@ import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ThemeUtil import eu.kanade.tachiyomi.util.system.bottomCutoutInset import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.topCutoutInset @@ -143,6 +145,12 @@ class PagerPageHolder( else -> ThemeUtil.readerBackgroundColor(theme) } ) + progressBar.foregroundTintList = ColorStateList.valueOf( + context.getResourceColor( + if (isInvertedFromTheme()) R.attr.colorPrimaryInverse + else R.attr.colorPrimary + ) + ) } /** @@ -456,7 +464,6 @@ class PagerPageHolder( /** * Creates a new progress bar. */ - @SuppressLint("PrivateResource") private fun createProgressBar(): ReaderProgressBar { return ReaderProgressBar(context, null).apply { val size = 48.dpToPx @@ -466,6 +473,14 @@ class PagerPageHolder( } } + private fun isInvertedFromTheme(): Boolean { + return when ((background as? ColorDrawable)?.color ?: Color.TRANSPARENT) { + Color.WHITE -> context.isInNightMode() + Color.BLACK -> !context.isInNightMode() + else -> false + } + } + /** * Initializes a subsampling scale view. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 7e5b7d7bee..2f2b0ee813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -67,12 +67,10 @@ import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setStyle import eu.kanade.tachiyomi.util.view.smoothScrollToTop import eu.kanade.tachiyomi.util.view.snack -import eu.kanade.tachiyomi.util.view.toolbarHeight import eu.kanade.tachiyomi.util.view.withFadeTransaction import java.util.Locale -import kotlin.math.abs import kotlin.math.max -import kotlin.math.min +import kotlin.math.roundToInt /** * Fragment that shows recently read manga. @@ -108,8 +106,6 @@ class RecentsController(bundle: Bundle? = null) : private var lastChapterId: Long? = null private var showingDownloads = false var headerHeight = 0 - val shadowAlpha = 0.15f - val shadow2Alpha = 0.05f private var query = "" set(value) { field = value @@ -130,7 +126,8 @@ class RecentsController(bundle: Bundle? = null) : ) } - override fun createBinding(inflater: LayoutInflater) = RecentsControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = + RecentsControllerBinding.inflate(inflater) /** * Called when view is created @@ -164,9 +161,6 @@ class RecentsController(bundle: Bundle? = null) : includeTabView = true, afterInsets = { headerHeight = it.systemWindowInsetTop + appBarHeight + 48.dpToPx - binding.fakeAppBar.updateLayoutParams { - height = it.systemWindowInsetTop + (toolbarHeight ?: appBarHeight) - } binding.recycler.updatePaddingRelative( bottom = activityBinding?.bottomNav?.height ?: it.systemWindowInsetBottom ) @@ -182,16 +176,11 @@ class RecentsController(bundle: Bundle? = null) : setBottomPadding() } ) - binding.recycler.addOnScrollListener( - object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - binding.fakeAppBar.y = activityBinding?.appBar?.y ?: 0f - } - } - ) activityBinding?.root?.post { - val height = activityBinding?.bottomNav?.height ?: view.rootWindowInsets?.systemWindowInsetBottom ?: 0 + val height = + activityBinding?.bottomNav?.height ?: view.rootWindowInsets?.systemWindowInsetBottom + ?: 0 binding.recycler.updatePaddingRelative(bottom = height) binding.downloadBottomSheet.dlRecycler.updatePaddingRelative( bottom = height @@ -200,11 +189,9 @@ class RecentsController(bundle: Bundle? = null) : activityBinding?.tabsFrameLayout?.isVisible = !isExpanded if (isExpanded) { (activity as? MainActivity)?.showTabBar(show = false, animate = false) + setRecentsAppBarBG(1f) } val isCollapsed = binding.downloadBottomSheet.root.sheetBehavior.isCollapsed() - binding.shadow2.alpha = if (isCollapsed) shadow2Alpha else 0f - binding.shadow.alpha = if (isCollapsed) shadowAlpha else 0f - binding.fakeAppBar.alpha = if (isExpanded) 1f else 0f binding.downloadBottomSheet.dlRecycler.alpha = isExpanded.toInt().toFloat() binding.downloadBottomSheet.sheetLayout.backgroundTintList = ColorStateList.valueOf( ColorUtils.blendARGB( @@ -213,7 +200,8 @@ class RecentsController(bundle: Bundle? = null) : isExpanded.toInt().toFloat() ) ) - binding.downloadBottomSheet.root.backgroundTintList = binding.downloadBottomSheet.sheetLayout.backgroundTintList + binding.downloadBottomSheet.root.backgroundTintList = + binding.downloadBottomSheet.sheetLayout.backgroundTintList updateTitleAndMenu() } @@ -225,42 +213,36 @@ class RecentsController(bundle: Bundle? = null) : binding.downloadBottomSheet.dlBottomSheet.onCreate(this) - binding.shadow2.alpha = - if (binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) shadow2Alpha else 0f - binding.shadow.alpha = - if (binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) shadowAlpha else 0f - binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.addBottomSheetCallback( object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, progress: Float) { - binding.shadow2.alpha = (1 - abs(progress)) * shadow2Alpha - binding.shadow.alpha = (1 - abs(progress)) * shadowAlpha val height = binding.root.height - binding.downloadBottomSheet.dlRecycler.paddingTop // Doing some fun math to hide the tab bar just as the title text of the // dl sheet is under the toolbar val cap = height * (1 / 12600f) + 479f / 700 - activityBinding?.appBar?.elevation = min( - (1f - progress / cap) * 15f, - if (binding.recycler.canScrollVertically(-1)) 15f else 0f - ).coerceIn(0f, 15f) - binding.fakeAppBar.alpha = max(0f, (progress - cap) / (1f - cap)) + val elValue = max( + max(0f, (progress - cap)) / (1 - cap), + if (binding.recycler.canScrollVertically(-1)) 1f else 0f + ).coerceIn(0f, 1f) + setRecentsAppBarBG(elValue) binding.downloadBottomSheet.sheetLayout.alpha = 1 - max(0f, progress / cap) binding.downloadBottomSheet.dlRecycler.alpha = progress * 10 - binding.downloadBottomSheet.sheetLayout.backgroundTintList = ColorStateList.valueOf( - ColorUtils.blendARGB( - view.context.getResourceColor(R.attr.colorPrimaryVariant), - view.context.getResourceColor(R.attr.background), - (progress * 2f).coerceIn(0f, 1f) + binding.downloadBottomSheet.sheetLayout.backgroundTintList = + ColorStateList.valueOf( + ColorUtils.blendARGB( + view.context.getResourceColor(R.attr.colorPrimaryVariant), + view.context.getResourceColor(R.attr.background), + (progress * 2f).coerceIn(0f, 1f) + ) ) - ) - binding.downloadBottomSheet.root.backgroundTintList = binding.downloadBottomSheet.sheetLayout.backgroundTintList + binding.downloadBottomSheet.root.backgroundTintList = + binding.downloadBottomSheet.sheetLayout.backgroundTintList activityBinding?.appBar?.y = max( activityBinding!!.appBar.y, -headerHeight * (1 - progress) ) - binding.fakeAppBar.y = activityBinding?.appBar?.y ?: 0f activityBinding?.tabsFrameLayout?.let { tabs -> tabs.alpha = 1 - max(0f, progress / cap) if (tabs.alpha <= 0 && tabs.isVisible) { @@ -320,12 +302,6 @@ class RecentsController(bundle: Bundle? = null) : binding.downloadBottomSheet.downloadFab.hide() } } - if (state == BottomSheetBehavior.STATE_HIDDEN || state == BottomSheetBehavior.STATE_COLLAPSED) { - binding.shadow2.alpha = - if (state == BottomSheetBehavior.STATE_COLLAPSED) shadow2Alpha else 0f - binding.shadow.alpha = - if (state == BottomSheetBehavior.STATE_COLLAPSED) shadowAlpha else 0f - } binding.downloadBottomSheet.sheetLayout.isClickable = state == BottomSheetBehavior.STATE_COLLAPSED @@ -379,11 +355,27 @@ class RecentsController(bundle: Bundle? = null) : fun updateTitleAndMenu() { if (router.backstack.lastOrNull()?.controller == this) { - (activity as? MainActivity)?.setFloatingToolbar(!showingDownloads, true) + val activity = (activity as? MainActivity) ?: return + activity.setFloatingToolbar(!showingDownloads, true) + if (showingDownloads) { + setRecentsAppBarBG(1f) + } setTitle() } } + fun setRecentsAppBarBG(value: Float) { + val context = view?.context ?: return + val color = ColorUtils.blendARGB( + context.getResourceColor(R.attr.colorSurface), + context.getResourceColor(R.attr.colorPrimaryVariant), + value + ) + activityBinding?.appBar?.setBackgroundColor(color) + activity?.window?.statusBarColor = + ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt()) + } + private fun setBottomPadding() { val bottomBar = activityBinding?.bottomNav val pad = bottomBar?.translationY?.minus(bottomBar.height) ?: 0f @@ -391,7 +383,6 @@ class RecentsController(bundle: Bundle? = null) : (-pad).toInt(), view?.rootWindowInsets?.getBottomGestureInsets() ?: 0 ) - binding.shadow2.translationY = pad binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.peekHeight = 48.spToPx + padding binding.downloadBottomSheet.fastScroller.updateLayoutParams { bottomMargin = -pad.toInt() @@ -787,7 +778,7 @@ class RecentsController(bundle: Bundle? = null) : else binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.expand() } - override fun sheetIsExpanded(): Boolean = binding.downloadBottomSheet.dlBottomSheet.sheetBehavior.isExpanded() + override fun sheetIsFullscreen(): Boolean = binding.downloadBottomSheet.dlBottomSheet.sheetBehavior.isExpanded() override fun expandSearch() { if (showingDownloads) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RemoveHistoryDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RemoveHistoryDialog.kt index 00175b4d37..5564b6d755 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RemoveHistoryDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RemoveHistoryDialog.kt @@ -2,15 +2,16 @@ package eu.kanade.tachiyomi.ui.recents import android.app.Dialog import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.checkbox.checkBoxPrompt -import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked import com.bluelinelabs.conductor.Controller import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.util.system.addCheckBoxPrompt +import eu.kanade.tachiyomi.util.system.isPromptChecked +import eu.kanade.tachiyomi.util.system.materialAlertDialog +import eu.kanade.tachiyomi.util.system.setCustomTitleAndMessage class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller, T : RemoveHistoryDialog.Listener { @@ -31,20 +32,26 @@ class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) override fun onCreateDialog(savedViewState: Bundle?): Dialog { val activity = activity!! - return MaterialDialog(activity).title(R.string.reset_chapter_question).message( - text = if (chapter?.name != null) activity.getString( - R.string.this_will_remove_the_read_date_for_x_question, - chapter?.name ?: "" + return activity.materialAlertDialog() + .setCustomTitleAndMessage( + R.string.reset_chapter_question, + if (chapter?.name != null) activity.getString( + R.string.this_will_remove_the_read_date_for_x_question, + chapter?.name ?: "" + ) + else activity.getString(R.string.this_will_remove_the_read_date_question) ) - else activity.getString(R.string.this_will_remove_the_read_date_question) - ).checkBoxPrompt( - text = activity.getString( - R.string.reset_all_chapters_for_this_, - manga!!.seriesType(activity) + .addCheckBoxPrompt( + activity.getString( + R.string.reset_all_chapters_for_this_, + manga!!.seriesType(activity) + ) ) - ) {}.negativeButton(android.R.string.cancel).positiveButton(R.string.reset) { - onPositive(it.isCheckPromptChecked()) - } + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.reset) { dialog, _ -> + onPositive(dialog.isPromptChecked) + } + .create() } private fun onPositive(checked: Boolean) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt index aaadd0bd69..6a3b5da418 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt @@ -6,7 +6,6 @@ import android.os.Build import android.os.Bundle import androidx.core.net.toUri import androidx.preference.PreferenceScreen -import com.afollestad.materialdialogs.MaterialDialog import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R @@ -18,6 +17,7 @@ import eu.kanade.tachiyomi.data.updater.UpdaterService import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.openInBrowser import kotlinx.coroutines.Dispatchers @@ -208,10 +208,10 @@ class AboutController : SettingsController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { val isOnA12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S - return MaterialDialog(activity!!) - .title(R.string.new_version_available) - .message(text = args.getString(BODY_KEY) ?: "") - .positiveButton(if (isOnA12) R.string.update else R.string.download) { + return activity!!.materialAlertDialog() + .setTitle(R.string.new_version_available) + .setMessage(args.getString(BODY_KEY) ?: "") + .setPositiveButton(if (isOnA12) R.string.update else R.string.download) { _, _ -> val appContext = applicationContext if (appContext != null) { // Start download @@ -219,7 +219,8 @@ class AboutController : SettingsController() { UpdaterService.start(appContext, url, true) } } - .negativeButton(R.string.ignore) + .setNegativeButton(R.string.ignore, null) + .create() } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 3a4f52335f..3eb2651068 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -8,12 +8,11 @@ import android.os.Bundle import android.os.PowerManager import android.provider.Settings import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceScreen -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsMultiChoice import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache @@ -30,8 +29,10 @@ import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.CrashLogUtil +import eu.kanade.tachiyomi.util.system.disableItems import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.openInBrowser import kotlinx.coroutines.CoroutineStart @@ -229,18 +230,32 @@ class SettingsAdvancedController : SettingsController() { } } - class CleanupDownloadsDialogController() : DialogController() { + class CleanupDownloadsDialogController : DialogController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialDialog(activity!!).show { - title(R.string.clean_up_downloaded_chapters) - .listItemsMultiChoice(R.array.clean_up_downloads, disabledIndices = intArrayOf(0), initialSelection = intArrayOf(0, 1, 2)) { dialog, selections, items -> - val deleteRead = selections.contains(1) - val deleteNonFavorite = selections.contains(2) - (targetController as? SettingsAdvancedController)?.cleanupDownloads(deleteRead, deleteNonFavorite) + return activity!!.materialAlertDialog() + .setTitle(R.string.clean_up_downloaded_chapters) + .setMultiChoiceItems( + R.array.clean_up_downloads, + booleanArrayOf(true, true, true) + ) { dialog, position, _ -> + if (position == 0) { + val listView = (dialog as AlertDialog).listView + listView.setItemChecked(position, true) } - positiveButton(android.R.string.ok) - negativeButton(android.R.string.cancel) - } + } + .setPositiveButton(android.R.string.ok) { dialog, _ -> + val listView = (dialog as AlertDialog).listView + val deleteRead = listView.isItemChecked(1) + val deleteNonFavorite = listView.isItemChecked(2) + (targetController as? SettingsAdvancedController)?.cleanupDownloads( + deleteRead, + deleteNonFavorite + ) + } + .setNegativeButton(android.R.string.cancel, null) + .create().apply { + this.disableItems(arrayOf(activity!!.getString(R.string.clean_orphaned_downloads))) + } } } @@ -322,12 +337,13 @@ class SettingsAdvancedController : SettingsController() { class ClearDatabaseDialogController : DialogController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialDialog(activity!!) - .message(R.string.clear_database_confirmation) - .positiveButton(android.R.string.ok) { + return activity!!.materialAlertDialog() + .setMessage(R.string.clear_database_confirmation) + .setPositiveButton(android.R.string.ok) { _, _ -> (targetController as? SettingsAdvancedController)?.clearDatabase() } - .negativeButton(android.R.string.cancel) + .setNegativeButton(android.R.string.cancel, null) + .create() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAppearanceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAppearanceController.kt index 471efe96ce..8582f32f4d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAppearanceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAppearanceController.kt @@ -82,6 +82,15 @@ class SettingsAppearanceController : SettingsController() { } } + preferenceCategory { + titleRes = R.string.details_page + switchPreference { + key = Keys.themeMangaDetails + titleRes = R.string.theme_buttons_based_on_cover + defaultValue = true + } + } + preferenceCategory { titleRes = R.string.navigation diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index bcfa14bf69..4b2daa2941 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -8,12 +8,11 @@ import android.net.Uri import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceScreen -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupConst @@ -27,7 +26,9 @@ import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.system.MiuiUtil +import eu.kanade.tachiyomi.util.system.disableItems import eu.kanade.tachiyomi.util.system.getFilePicker +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import kotlinx.coroutines.flow.launchIn @@ -245,29 +246,37 @@ class SettingsBackupController : SettingsController() { ) .map { activity.getString(it) } - return MaterialDialog(activity) - .title(R.string.create_backup) - .message(R.string.what_should_backup) - .listItemsMultiChoice( - items = options, - disabledIndices = intArrayOf(0), - initialSelection = intArrayOf(0, 1, 2, 3, 4, 5) - ) { _, positions, _ -> + return activity.materialAlertDialog() + .setTitle(R.string.what_should_backup) + .setMultiChoiceItems( + options.toTypedArray(), + booleanArrayOf(true, true, true, true, true, true) + ) { dialog, position, _ -> + if (position == 0) { + val listView = (dialog as AlertDialog).listView + listView.setItemChecked(position, true) + } + } + .setPositiveButton(R.string.create) { dialog, _ -> + val listView = (dialog as AlertDialog).listView var flags = 0 - for (i in 1 until positions.size) { - when (positions[i]) { - 1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY - 2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER - 3 -> flags = flags or BackupCreateService.BACKUP_TRACK - 4 -> flags = flags or BackupCreateService.BACKUP_HISTORY - 5 -> flags = flags or BackupCreateService.BACKUP_CUSTOM_INFO + for (i in 1 until listView.count) { + if (listView.isItemChecked(i)) { + when (i) { + 1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY + 2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER + 3 -> flags = flags or BackupCreateService.BACKUP_TRACK + 4 -> flags = flags or BackupCreateService.BACKUP_HISTORY + 5 -> flags = flags or BackupCreateService.BACKUP_CUSTOM_INFO + } } } - (targetController as? SettingsBackupController)?.createBackup(flags) } - .positiveButton(R.string.create) - .negativeButton(android.R.string.cancel) + .setNegativeButton(android.R.string.cancel, null) + .create().apply { + disableItems(arrayOf(options.first())) + } } } @@ -305,21 +314,22 @@ class SettingsBackupController : SettingsController() { message += "\n\n${activity.getString(R.string.restore_missing_trackers)}\n${results.missingTrackers.joinToString("\n") { "- $it" }}" } - return MaterialDialog(activity) - .title(R.string.restore_backup) - .message(text = message) - .positiveButton(R.string.restore) { + return activity.materialAlertDialog() + .setTitle(R.string.restore_backup) + .setMessage(message) + .setPositiveButton(R.string.restore) { _, _ -> val context = applicationContext if (context != null) { activity.toast(R.string.restoring_backup) BackupRestoreService.start(context, uri, type) } - } + }.create() } catch (e: Exception) { - MaterialDialog(activity) - .title(R.string.invalid_backup_file) - .message(text = e.message) - .positiveButton(android.R.string.cancel) + activity.materialAlertDialog() + .setTitle(R.string.invalid_backup_file) + .setMessage(e.message) + .setPositiveButton(android.R.string.cancel, null) + .create() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index b9d73ff8f8..ee87212311 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -1,17 +1,14 @@ package eu.kanade.tachiyomi.ui.setting import android.app.Activity -import android.app.Dialog import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri -import android.os.Bundle import android.os.Environment import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.preference.PreferenceScreen -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -19,8 +16,8 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.system.getFilePicker +import eu.kanade.tachiyomi.util.system.withOriginalWidth import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -38,9 +35,7 @@ class SettingsDownloadController : SettingsController() { key = Keys.downloadsDirectory titleRes = R.string.download_location onClick { - val ctrl = DownloadDirectoriesDialog() - ctrl.targetController = this@SettingsDownloadController - ctrl.showDialog(router) + DownloadDirectoriesDialog(this@SettingsDownloadController).show() } preferences.downloadsDirectory().asObservable() @@ -151,34 +146,39 @@ class SettingsDownloadController : SettingsController() { } } - class DownloadDirectoriesDialog : DialogController() { + class DownloadDirectoriesDialog(val controller: SettingsDownloadController) : + MaterialAlertDialogBuilder(controller.activity!!.withOriginalWidth()) { private val preferences: PreferencesHelper = Injekt.get() - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val activity = activity!! - val currentDir = preferences.downloadsDirectory().getOrDefault() - val externalDirs = getExternalDirs() + File(activity.getString(R.string.custom_location)) - val selectedIndex = externalDirs.map(File::toString).indexOfFirst { it in currentDir } + val activity = controller.activity!! - return MaterialDialog(activity) - .listItemsSingleChoice(items = externalDirs.map { it.path }, initialSelection = selectedIndex) { _, position, text -> - val target = targetController as? SettingsDownloadController - if (position == externalDirs.lastIndex) { - target?.customDirectorySelected(currentDir) - } else { - target?.predefinedDirectorySelected(text.toString()) - } + init { + val currentDir = preferences.downloadsDirectory().getOrDefault() + val externalDirs = + getExternalDirs() + File(activity.getString(R.string.custom_location)) + val selectedIndex = externalDirs.map(File::toString).indexOfFirst { it in currentDir } + val items = externalDirs.map { it.path } + + setTitle(R.string.download_location) + setSingleChoiceItems(items.toTypedArray(), selectedIndex) { dialog, position -> + if (position == externalDirs.lastIndex) { + controller.customDirectorySelected(currentDir) + } else { + controller.predefinedDirectorySelected(items[position]) } + dialog.dismiss() + } + setNegativeButton(android.R.string.cancel, null) } private fun getExternalDirs(): List { val defaultDir = Environment.getExternalStorageDirectory().absolutePath + - File.separator + resources?.getString(R.string.app_name) + + File.separator + activity.resources?.getString(R.string.app_name) + File.separator + "downloads" return mutableListOf(File(defaultDir)) + - ContextCompat.getExternalFilesDirs(activity!!, "").filterNotNull() + ContextCompat.getExternalFilesDirs(activity, "").filterNotNull() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt index 5e3c28f718..0dede0788c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.source import android.animation.ValueAnimator import android.app.Activity -import android.content.res.ColorStateList +import android.graphics.Color import android.os.Parcelable import android.view.LayoutInflater import android.view.Menu @@ -60,8 +60,8 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date import java.util.Locale -import kotlin.math.abs import kotlin.math.max +import kotlin.math.roundToInt /** * This controller shows and manages the different catalogues enabled by the user. @@ -95,8 +95,6 @@ class BrowseController : var showingExtensions = false var snackbar: Snackbar? = null - val shadowAlpha = 0.15f - val shadow2Alpha = 0.05f /** * Called when controller is initialized. @@ -160,7 +158,6 @@ class BrowseController : bottom = (activityBinding?.bottomNav?.height ?: 0) + 58.spToPx ) val isCollapsed = binding.bottomSheet.root.sheetBehavior.isCollapsed() - binding.shadow.alpha = if (isCollapsed) shadowAlpha else 0f updateTitleAndMenu() } @@ -169,15 +166,10 @@ class BrowseController : binding.bottomSheet.root.sheetBehavior?.isGestureInsetBottomIgnored = true - binding.shadow.alpha = - if (binding.bottomSheet.root.sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) shadowAlpha else 0f - binding.bottomSheet.root.sheetBehavior?.addBottomSheetCallback( object : BottomSheetBehavior .BottomSheetCallback() { override fun onSlide(bottomSheet: View, progress: Float) { - binding.shadow2.alpha = (1 - max(0f, progress)) * shadow2Alpha - binding.shadow.alpha = (1 - abs(progress)) * shadowAlpha activityBinding?.appBar?.y = max(activityBinding!!.appBar.y, -headerHeight * (1 - progress)) val oldShow = showingExtensions showingExtensions = progress > 0.92f @@ -209,11 +201,6 @@ class BrowseController : } else extBottomSheet.shouldCallApi = true } - if (state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_COLLAPSED) { - binding.shadow.alpha = - if (state == BottomSheetBehavior.STATE_COLLAPSED) shadowAlpha else 0f - } - retainViewMode = if (state == BottomSheetBehavior.STATE_EXPANDED) { RetainViewMode.RETAIN_DETACH } else RetainViewMode.RELEASE_DETACH @@ -237,8 +224,20 @@ class BrowseController : fun updateTitleAndMenu() { if (router.backstack.lastOrNull()?.controller == this) { + val activity = (activity as? MainActivity) ?: return (activity as? MainActivity)?.setFloatingToolbar(!showingExtensions) - activity?.invalidateOptionsMenu() + if (showingExtensions) { + val color = activity.getResourceColor(R.attr.colorPrimaryVariant) + activityBinding?.appBar?.setBackgroundColor(color) + activity.window?.statusBarColor = + ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt()) + } else { + activityBinding?.appBar?.setBackgroundColor(Color.TRANSPARENT) + activity.window?.statusBarColor = activity.getResourceColor( + android.R.attr.statusBarColor + ) + } + activity.invalidateOptionsMenu() setTitle() } } @@ -258,7 +257,7 @@ class BrowseController : bottomSheet.context.getResourceColor(R.attr.actionBarTintColor), 153 ) - binding.bottomSheet.sheetLayout.elevation = progress * 5 +// binding.bottomSheet.sheetLayout.elevation = progress * 5 binding.bottomSheet.pager.alpha = progress * 10 binding.bottomSheet.tabs.setSelectedTabIndicatorColor(selectedColor) binding.bottomSheet.tabs.setTabTextColors( @@ -274,13 +273,13 @@ class BrowseController : ) ) - binding.bottomSheet.sheetLayout.backgroundTintList = ColorStateList.valueOf( + /*binding.bottomSheet.sheetLayout.backgroundTintList = ColorStateList.valueOf( ColorUtils.blendARGB( bottomSheet.context.getResourceColor(R.attr.colorPrimaryVariant), bottomSheet.context.getResourceColor(R.attr.colorSurface), progress ) - ) + )*/ } private fun setBottomPadding() { @@ -290,7 +289,6 @@ class BrowseController : (-pad).toInt(), view?.rootWindowInsets?.getBottomGestureInsets() ?: 0 ) - binding.shadow2.translationY = pad binding.bottomSheet.root.sheetBehavior?.peekHeight = 56.spToPx + padding binding.bottomSheet.root.extensionFrameLayout?.binding?.fastScroller?.updateLayoutParams { bottomMargin = -pad.toInt() @@ -324,7 +322,7 @@ class BrowseController : } } - override fun sheetIsExpanded(): Boolean = binding.bottomSheet.root.sheetBehavior.isExpanded() + override fun sheetIsFullscreen(): Boolean = binding.bottomSheet.root.sheetBehavior.isExpanded() override fun handleSheetBack(): Boolean { if (showingExtensions) { @@ -367,6 +365,7 @@ class BrowseController : if (type.isEnter) { binding.bottomSheet.root.canExpand = true setBottomPadding() + updateTitleAndMenu() } } @@ -535,9 +534,9 @@ class BrowseController : SettingsExtensionsController() } else SettingsSourcesController() router.pushController( - (RouterTransaction.with(controller)).popChangeHandler( - SettingsSourcesFadeChangeHandler() - ).pushChangeHandler(FadeChangeHandler()) + RouterTransaction.with(controller) + .popChangeHandler(SettingsSourcesFadeChangeHandler()) + .pushChangeHandler(FadeChangeHandler()) ) } R.id.action_migration_guide -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt index 720af43673..d9658f32c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt @@ -6,7 +6,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.RecyclerView import com.tfcporciuncula.flow.Preference @@ -16,6 +15,7 @@ import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.MangaGridItemBinding +import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.widget.AutofitRecyclerView @@ -51,9 +51,8 @@ class BrowseSourceItem( bottomMargin = 6.dpToPx } } else { - binding.constraintLayout.background = ContextCompat.getDrawable( - context, - R.drawable.library_confortable_grid_selector + binding.constraintLayout.background = context.contextCompatDrawable( + R.drawable.library_comfortable_grid_selector ) } binding.constraintLayout.layoutParams = FrameLayout.LayoutParams( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt index a53185608c..8c869e07b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt @@ -38,7 +38,7 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem Filter.TriState.STATE_IGNORE TriStateCheckBox.State.CHECKED -> Filter.TriState.STATE_INCLUDE - TriStateCheckBox.State.INVERSED -> Filter.TriState.STATE_EXCLUDE + else -> Filter.TriState.STATE_EXCLUDE } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt index d2aba7b2f6..ab6632fa6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.util.view.makeShapeCorners class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) : BaseFlexibleViewHolder(view, adapter) { @@ -23,6 +24,8 @@ class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) : adapter.mangaClickListener.onMangaClick(item.manga) } } + binding.favoriteButton.shapeAppearanceModel = + binding.card.makeShapeCorners(binding.card.radius, binding.card.radius) itemView.setOnLongClickListener { adapter.mangaClickListener.onMangaLongClick(flexibleAdapterPosition, adapter) true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt index 501234c408..04db6ac089 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.webview +import android.annotation.SuppressLint import android.app.assist.AssistContent import android.content.res.Configuration import android.graphics.Color @@ -174,6 +175,7 @@ open class BaseWebViewActivity : BaseActivity() { binding.webview.reload() } + @SuppressLint("ResourceType") override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) val lightMode = !isInNightMode() @@ -185,64 +187,47 @@ open class BaseWebViewActivity : BaseActivity() { setTheme(R.style.ThemeOverlay_Tachiyomi_AllBlue) } } - window.statusBarColor = ColorUtils.setAlphaComponent( - getResourceColor(R.attr.colorSurface), - 255 + val themeValue = TypedValue() + theme.resolveAttribute(android.R.attr.windowLightStatusBar, themeValue, true) + + val wic = WindowInsetsControllerCompat(window, window.decorView) + wic.isAppearanceLightStatusBars = themeValue.data == -1 + wic.isAppearanceLightNavigationBars = themeValue.data == -1 + + val attrs = theme.obtainStyledAttributes( + intArrayOf( + R.attr.colorSurface, + R.attr.actionBarTintColor, + R.attr.colorPrimaryVariant, + ) ) setWebDarkMode() - binding.toolbar.setBackgroundColor(getResourceColor(R.attr.colorSurface)) - binding.toolbar.popupTheme = if (lightMode) R.style.ThemeOverlay_MaterialComponents else R - .style.ThemeOverlay_MaterialComponents_Dark - val tintColor = getResourceColor(R.attr.actionBarTintColor) - binding.toolbar.navigationIcon?.setTint(tintColor) + val colorSurface = attrs.getColor(0, 0) + val actionBarTintColor = attrs.getColor(1, 0) + val colorPrimaryVariant = attrs.getColor(2, 0) + attrs.recycle() + + window.statusBarColor = ColorUtils.setAlphaComponent(colorSurface, 255) + binding.toolbar.setBackgroundColor(colorSurface) + binding.toolbar.popupTheme = + if (lightMode) R.style.ThemeOverlay_Material3 + else R.style.ThemeOverlay_Material3_Dark + binding.toolbar.setNavigationIconTint(actionBarTintColor) binding.toolbar.overflowIcon?.mutate() - binding.toolbar.setTitleTextColor(tintColor) - binding.toolbar.overflowIcon?.setTint(tintColor) - binding.swipeRefresh.setStyle() + binding.toolbar.setTitleTextColor(actionBarTintColor) + binding.toolbar.overflowIcon?.setTint(actionBarTintColor) + binding.swipeRefresh.setColorSchemeColors(actionBarTintColor) + binding.swipeRefresh.setProgressBackgroundColorSchemeColor(colorPrimaryVariant) window.navigationBarColor = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) getResourceColor(R.attr.colorPrimaryVariant) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O || !lightMode) colorPrimaryVariant else Color.BLACK binding.webLinearLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val lightNav = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - val typedValue = TypedValue() - theme.resolveAttribute( - android.R.attr.windowLightNavigationBar, - typedValue, - true - ) - typedValue.data == -1 - } else { - lightMode - } - if (lightNav) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or( - View - .SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - ) - } else { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.rem( - View - .SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - ) - } - } - - val typedValue = TypedValue() - theme.resolveAttribute(android.R.attr.windowLightStatusBar, typedValue, true) - if (typedValue.data == -1) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - } else { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility - .rem(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - } } + override fun onBackPressed() { if (binding.webview.canGoBack()) binding.webview.goBack() else super.onBackPressed() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt index 5344564d93..83b28aff11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.graphics.Bitmap -import android.graphics.Color import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -12,7 +11,6 @@ import android.view.View import android.view.ViewGroup import android.webkit.WebView import android.widget.LinearLayout -import androidx.annotation.ColorInt import androidx.core.graphics.ColorUtils import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.SourceManager @@ -88,16 +86,6 @@ open class WebViewActivity : BaseWebViewActivity() { } } - @ColorInt - fun parseHTMLColor(color: String): Int { - val trimmedColor = color.trim('"') - val rgb = Regex("""^rgb\((\d+),\s*(\d+),\s*(\d+)\)$""").find(trimmedColor) - val red = rgb?.groupValues?.getOrNull(1)?.toIntOrNull() ?: return getResourceColor(R.attr.background) - val green = rgb.groupValues.getOrNull(2)?.toIntOrNull() ?: return getResourceColor(R.attr.background) - val blue = rgb.groupValues.getOrNull(3)?.toIntOrNull() ?: return getResourceColor(R.attr.background) - return Color.rgb(red, green, blue) - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) invalidateOptionsMenu() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index 33e985344c..8d0bb6389d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.view.snack +import eu.kanade.tachiyomi.widget.TriStateCheckBox import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -74,17 +75,24 @@ fun List.moveCategories( onMangaMoved: () -> Unit ) { if (this.isEmpty()) return - val commonCategories = this - .map { db.getCategoriesForManga(it).executeAsBlocking() } - .reduce { set1: Iterable, set2 -> set1.intersect(set2).toMutableList() } - .mapNotNull { it.id } - .toTypedArray() val categories = db.getCategories().executeAsBlocking() + val commonCategories = map { db.getCategoriesForManga(it).executeAsBlocking() } + .reduce { set1: Iterable, set2 -> set1.intersect(set2).toMutableList() } + .toTypedArray() + val mangaCategories = map { db.getCategoriesForManga(it).executeAsBlocking() } + val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2).toMutableList() } + val mixedCategories = mangaCategories.flatten().distinct().subtract(common).toMutableList() SetCategoriesSheet( activity, this, categories.toMutableList(), - commonCategories, + categories.map { + when (it) { + in commonCategories -> TriStateCheckBox.State.CHECKED + in mixedCategories -> TriStateCheckBox.State.INDETERMINATE + else -> TriStateCheckBox.State.UNCHECKED + } + }.toTypedArray(), false ) { onMangaMoved() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt index ed3335ad81..02c24f31c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.util.chapter import android.content.Context import android.content.res.ColorStateList import android.widget.TextView +import androidx.core.widget.TextViewCompat import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter @@ -36,7 +37,7 @@ class ChapterUtil { } } - fun setBookmark(textView: TextView, chapter: Chapter) { + private fun setBookmark(textView: TextView, chapter: Chapter) { if (chapter.bookmark) { val context = textView.context val drawable = VectorDrawableCompat.create( @@ -51,8 +52,9 @@ class ChapterUtil { null, null ) - textView.compoundDrawableTintList = ColorStateList.valueOf( - bookmarkedColor(context) + TextViewCompat.setCompoundDrawableTintList( + textView, + ColorStateList.valueOf(bookmarkedColor(context)) ) textView.compoundDrawablePadding = 3.dpToPx textView.translationX = (-2f).dpToPxEnd diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 2e334f66bd..f312bf5a93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -36,6 +36,7 @@ import androidx.core.net.toUri import com.nononsenseapps.filepicker.FilePickerActivity import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -103,6 +104,7 @@ fun Context.hasPermission(permission: String) = * * @param resource the attribute. */ +@ColorInt fun Context.getResourceColor(@AttrRes resource: Int): Int { val typedArray = obtainStyledAttributes(intArrayOf(resource)) val attrValue = typedArray.getColor(0, 0) @@ -197,6 +199,20 @@ fun Context.prepareSideNavContext(): Context { return this } +fun Context.withOriginalWidth(): Context { + val width = (this as? MainActivity)?.ogWidth ?: resources.configuration.screenWidthDp + val configuration = resources.configuration + val overrideConf = Configuration() + overrideConf.setTo(configuration) + overrideConf.screenWidthDp = width + resources.configuration.updateFrom(overrideConf) + return this +} + +fun Context.isLandscape(): Boolean { + return resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE +} + /** * Convenience method to acquire a partial wake lock. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DateExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DateExtensions.kt index 1dc1fe50bd..7532d3a312 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DateExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DateExtensions.kt @@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.util.system import android.content.Context import android.text.format.DateUtils import eu.kanade.tachiyomi.R +import java.util.Calendar import java.util.Locale +import java.util.TimeZone val Long.timeSpanFromNow: String get() = DateUtils.getRelativeTimeSpanString(this).toString() @@ -15,3 +17,53 @@ fun Long.timeSpanFromNow(context: Context): String { DateUtils.getRelativeTimeSpanString(this).toString() } } + +/** + * Convert local time millisecond value to Calendar instance in UTC + * + * @return UTC Calendar instance at supplied time. Null if time is 0. + */ +fun Long.toUtcCalendar(): Calendar? { + if (this == 0L) { + return null + } + val rawCalendar = Calendar.getInstance().apply { + timeInMillis = this@toUtcCalendar + } + return Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { + clear() + set( + rawCalendar.get(Calendar.YEAR), + rawCalendar.get(Calendar.MONTH), + rawCalendar.get(Calendar.DAY_OF_MONTH), + rawCalendar.get(Calendar.HOUR_OF_DAY), + rawCalendar.get(Calendar.MINUTE), + rawCalendar.get(Calendar.SECOND) + ) + } +} + +/** + * Convert UTC time millisecond to Calendar instance in local time zone + * + * @return local Calendar instance at supplied UTC time. Null if time is 0. + */ +fun Long.toLocalCalendar(): Calendar? { + if (this == 0L) { + return null + } + val rawCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { + timeInMillis = this@toLocalCalendar + } + return Calendar.getInstance().apply { + clear() + set( + rawCalendar.get(Calendar.YEAR), + rawCalendar.get(Calendar.MONTH), + rawCalendar.get(Calendar.DAY_OF_MONTH), + rawCalendar.get(Calendar.HOUR_OF_DAY), + rawCalendar.get(Calendar.MINUTE), + rawCalendar.get(Calendar.SECOND) + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt new file mode 100644 index 0000000000..4839e9b291 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt @@ -0,0 +1,138 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.Context +import android.content.DialogInterface +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.CheckResult +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatCheckedTextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import eu.kanade.tachiyomi.databinding.CustomDialogTitleMessageBinding +import eu.kanade.tachiyomi.databinding.DialogQuadstateBinding +import eu.kanade.tachiyomi.widget.TriStateCheckBox +import eu.kanade.tachiyomi.widget.materialdialogs.TriStateMultiChoiceDialogAdapter +import eu.kanade.tachiyomi.widget.materialdialogs.TriStateMultiChoiceListener + +fun Context.materialAlertDialog() = MaterialAlertDialogBuilder(withOriginalWidth()) + +fun MaterialAlertDialogBuilder.addCheckBoxPrompt( + @StringRes stringRes: Int, + isChecked: Boolean = false, + listener: MaterialAlertDialogBuilderOnCheckClickListener? = null +): MaterialAlertDialogBuilder { + return addCheckBoxPrompt(context.getString(stringRes), isChecked, listener) +} + +fun MaterialAlertDialogBuilder.addCheckBoxPrompt( + text: CharSequence, + isChecked: Boolean = false, + listener: MaterialAlertDialogBuilderOnCheckClickListener? = null +): MaterialAlertDialogBuilder { + return setMultiChoiceItems( + arrayOf(text), + booleanArrayOf(isChecked) + ) { dialog, _, checked -> + listener?.onClick(dialog, checked) + } +} + +fun AlertDialog.disableItems(items: Array) { + val listView = listView ?: return + listView.setOnHierarchyChangeListener( + object : ViewGroup.OnHierarchyChangeListener { + override fun onChildViewAdded(parent: View?, child: View) { + val text = (child as? AppCompatCheckedTextView)?.text ?: return + if (items.contains(text)) { + child.setOnClickListener(null) + child.isEnabled = false + } else { + child.isEnabled = true + } + } + + override fun onChildViewRemoved(view: View?, view1: View?) {} + } + ) +} + +fun MaterialAlertDialogBuilder.setCustomTitleAndMessage(title: Int, message: String): MaterialAlertDialogBuilder { + return setCustomTitle( + (CustomDialogTitleMessageBinding.inflate(LayoutInflater.from(context))).apply { + alertTitle.text = context.getString(title) + this.message.text = message + }.root + ) +} + +/** + * A variant of listItemsMultiChoice that allows for checkboxes that supports 3 states instead. + */ +@CheckResult +internal fun MaterialAlertDialogBuilder.setTriStateItems( + message: String? = null, + items: List, + disabledIndices: IntArray? = null, + initialSelection: IntArray = IntArray(items.size), + skipChecked: Boolean = false, + selection: TriStateMultiChoiceListener?, +): MaterialAlertDialogBuilder { + val binding = DialogQuadstateBinding.inflate(LayoutInflater.from(context)) + binding.list.layoutManager = LinearLayoutManager(context) + binding.list.adapter = TriStateMultiChoiceDialogAdapter( + dialog = this, + items = items, + disabledItems = disabledIndices, + initialSelection = initialSelection, + skipChecked = skipChecked, + listener = selection + ) + val updateScrollIndicators = { + binding.scrollIndicatorUp.isVisible = binding.list.canScrollVertically(-1) + binding.scrollIndicatorDown.isVisible = binding.list.canScrollVertically(1) + } + binding.list.setOnScrollChangeListener { _, _, _, _, _ -> + updateScrollIndicators() + } + binding.list.post { + updateScrollIndicators() + } + + if (message != null) { + binding.message.text = message + binding.message.isVisible = true + } + return setView(binding.root) +} + +internal fun MaterialAlertDialogBuilder.setNegativeStateItems( + items: List, + initialSelection: BooleanArray = BooleanArray(items.size), + listener: DialogInterface.OnMultiChoiceClickListener +): MaterialAlertDialogBuilder { + return setTriStateItems( + items = items, + initialSelection = initialSelection.map { + if (it) { + TriStateCheckBox.State.INVERSED.ordinal + } else { + TriStateCheckBox.State.UNCHECKED.ordinal + } + } + .toIntArray(), + skipChecked = true + ) { _, _, _, index, state -> + listener.onClick(null, index, state == TriStateCheckBox.State.INVERSED.ordinal) + } +} + +val DialogInterface.isPromptChecked: Boolean + get() = (this as? AlertDialog)?.listView?.isItemChecked(0) ?: false + +fun interface MaterialAlertDialogBuilderOnCheckClickListener { + fun onClick(var1: DialogInterface?, var3: Boolean) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt index e22df21da3..3b1d465cc0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt @@ -5,6 +5,8 @@ import android.view.View import android.view.WindowInsets import androidx.annotation.RequiresApi import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.mandatorySystemGestures +import androidx.core.view.WindowInsetsCompat.Type.systemBars fun WindowInsets.getBottomGestureInsets(): Int { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mandatorySystemGestureInsets.bottom @@ -17,7 +19,14 @@ fun WindowInsets.isBottomTappable() = ( systemWindowInsetBottom != tappableElementInsets.bottom ) -fun WindowInsets.hasSideInsets() = systemWindowInsetLeft > 0 || systemWindowInsetRight > 0 +fun WindowInsetsCompat.getBottomGestureInsets(): Int { + return getInsetsIgnoringVisibility(mandatorySystemGestures() or systemBars()).bottom +} + +/** returns if device using gesture nav and supports true edge to edge */ +fun WindowInsetsCompat.isBottomTappable() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + getInsets(systemBars()).bottom != getInsets(mandatorySystemGestures()).bottom val View.rootWindowInsetsCompat get() = rootWindowInsets?.let { WindowInsetsCompat.toWindowInsetsCompat(it) } @@ -27,6 +36,10 @@ fun WindowInsets.hasSideNavBar() = (systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() && systemWindowInsetBottom == 0 +fun WindowInsetsCompat.hasSideNavBar() = + (getInsets(systemBars()).left > 0 || getInsets(systemBars()).right > 0) && !isBottomTappable() && + getInsets(systemBars()).bottom == 0 + @RequiresApi(Build.VERSION_CODES.R) fun WindowInsets.isImeVisible() = isVisible(WindowInsets.Type.ime()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index db4653742e..9a4353ac35 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -5,7 +5,9 @@ import android.animation.ValueAnimator import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.res.ColorStateList import android.content.res.Configuration +import android.graphics.Color import android.os.Build import android.os.Environment import android.provider.Settings @@ -17,14 +19,15 @@ import android.view.WindowInsets import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout import androidx.appcompat.widget.SearchView +import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils import androidx.core.math.MathUtils import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType @@ -40,9 +43,11 @@ import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.isTablet +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.injectLazy import kotlin.math.abs +import kotlin.math.roundToInt import kotlin.random.Random fun Controller.setOnQueryTextChangeListener( @@ -112,9 +117,7 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) recycler.applyBottomAnimatedInsets(setPadding = true) recycler.doOnApplyWindowInsets { view, insets, _ -> val headerHeight = insets.systemWindowInsetTop + appBarHeight - view.updatePaddingRelative( - top = headerHeight - ) + view.updatePaddingRelative(top = headerHeight) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { view.updatePaddingRelative( bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -139,6 +142,7 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) !(activityBinding?.toolbar?.isVisible == true || activityBinding?.tabsFrameLayout?.isVisible == true) if (floatingBar) { activityBinding?.appBar?.elevation = 0f + setAppBarBG(0f) return@f } elevationAnim = ValueAnimator.ofFloat( @@ -146,6 +150,7 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) if (el) 15f else 0f ) elevationAnim?.addUpdateListener { valueAnimator -> + setAppBarBG(valueAnimator.animatedValue as Float / 15f) activityBinding?.appBar?.elevation = valueAnimator.animatedValue as Float } elevationAnim?.start() @@ -154,6 +159,7 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) val floatingBar = !(activityBinding?.toolbar?.isVisible == true || activityBinding?.tabsFrameLayout?.isVisible == true) if (floatingBar) { + setAppBarBG(0f) activityBinding?.appBar?.elevation = 0f } elevateFunc(recycler.canScrollVertically(-1)) @@ -233,6 +239,7 @@ fun Controller.scrollViewWith( statusBarHeight = insets.systemWindowInsetTop afterInsets?.invoke(insets) } + var elevationAnim: ValueAnimator? = null var elevate = false var isInView = true @@ -246,6 +253,7 @@ fun Controller.scrollViewWith( val floatingBar = (this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView if (floatingBar) { + setAppBarBG(0f, includeTabView) activityBinding?.appBar?.elevation = 0f return@f } @@ -254,12 +262,14 @@ fun Controller.scrollViewWith( if (el) 15f else 0f ) elevationAnim?.addUpdateListener { valueAnimator -> + setAppBarBG(valueAnimator.animatedValue as Float / 15f, includeTabView) activityBinding?.appBar?.elevation = valueAnimator.animatedValue as Float } elevationAnim?.start() } } if ((this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView) { + setAppBarBG(0f, includeTabView) activityBinding?.appBar?.elevation = 0f } addLifecycleListener( @@ -286,7 +296,7 @@ fun Controller.scrollViewWith( lastY = 0f activityBinding!!.toolbar.tag = randomTag activityBinding!!.toolbar.setOnClickListener { - if ((this@scrollViewWith as? BottomSheetController)?.sheetIsExpanded() != true) { + if ((this@scrollViewWith as? BottomSheetController)?.sheetIsFullscreen() != true) { recycler.smoothScrollToTop() } else { (this@scrollViewWith as? BottomSheetController)?.toggleSheet() @@ -466,6 +476,39 @@ fun Controller.scrollViewWith( return elevateFunc } +fun Controller.setAppBarBG(value: Float, includeTabView: Boolean = false) { + val context = view?.context ?: return + val floatingBar = + (this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView + if ((this as? BottomSheetController)?.sheetIsFullscreen() == true) return + if (floatingBar) { + (activityBinding?.cardView as? CardView)?.setCardBackgroundColor(context.getResourceColor(R.attr.colorPrimaryVariant)) + activityBinding?.appBar?.setBackgroundColor(Color.TRANSPARENT) + activity?.window?.statusBarColor = context.getResourceColor(android.R.attr.statusBarColor) + } else { + val color = ColorUtils.blendARGB( + context.getResourceColor(R.attr.colorSurface), + context.getResourceColor(R.attr.colorPrimaryVariant), + value + ) + activityBinding?.appBar?.setBackgroundColor(color) + activity?.window?.statusBarColor = + ColorUtils.setAlphaComponent(color, (0.87f * 255).roundToInt()) + if ((this as? FloatingSearchInterface)?.showFloatingBar() == true) { + val invColor = ColorUtils.blendARGB( + context.getResourceColor(R.attr.colorSurface), + context.getResourceColor(R.attr.colorPrimaryVariant), + 1 - value + ) + (activityBinding?.cardView as? CardView)?.setCardBackgroundColor( + ColorStateList.valueOf( + invColor + ) + ) + } + } +} + fun Controller.requestPermissionsSafe(permissions: Array, requestCode: Int) { val activity = activity ?: return permissions.forEach { permission -> @@ -497,11 +540,11 @@ fun Controller.requestFilePermissionsSafe( (!preferences.hasDeniedA11FilePermission().get() || showA11PermissionAnyway) ) { preferences.hasDeniedA11FilePermission().set(true) - MaterialDialog(activity) - .title(R.string.all_files_permission_required) - .message(R.string.external_storage_permission_notice) - .cancelOnTouchOutside(false) - .positiveButton(android.R.string.ok) { + activity.materialAlertDialog() + .setTitle(R.string.all_files_permission_required) + .setMessage(R.string.external_storage_permission_notice) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> val intent = Intent( Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, "package:${activity.packageName}".toUri() @@ -513,7 +556,7 @@ fun Controller.requestFilePermissionsSafe( activity.startActivity(intent2) } } - .negativeButton(android.R.string.cancel) + .setNegativeButton(android.R.string.cancel, null) .show() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/MaterialDialogExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/MaterialDialogExtensions.kt deleted file mode 100644 index 27c289b644..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/MaterialDialogExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.util.view - -import com.afollestad.date.DatePicker -import com.afollestad.materialdialogs.MaterialDialog -import java.util.Calendar - -fun MaterialDialog.setDate(date: Long) { - val datePicker = findViewById(com.afollestad.materialdialogs.datetime.R.id.datetimeDatePicker) ?: return - val calendar = Calendar.getInstance() - calendar.timeInMillis = date - datePicker.setDate(calendar) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index d39a5836dd..20add48229 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -23,7 +23,7 @@ import android.view.WindowInsets import android.widget.Button import android.widget.FrameLayout import android.widget.TextView -import androidx.annotation.ColorRes +import androidx.annotation.Dimension import androidx.annotation.FloatRange import androidx.annotation.IdRes import androidx.annotation.RequiresApi @@ -45,15 +45,19 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.SmoothScroller import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView import com.google.android.material.navigation.NavigationBarItemView import com.google.android.material.navigation.NavigationBarMenuView import com.google.android.material.navigation.NavigationBarView +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.tintText import eu.kanade.tachiyomi.util.system.ThemeUtil -import eu.kanade.tachiyomi.util.system.contextCompatColor +import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.system.pxToDp import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlin.math.max @@ -283,19 +287,10 @@ fun SwipeRefreshLayout.setStyle() { fun MaterialButton.resetStrokeColor() { strokeColor = ColorStateList.valueOf( - ColorUtils.setAlphaComponent( - context.getResourceColor( - R.attr.colorOnSurface - ), - 31 - ) + ColorUtils.setAlphaComponent(context.getResourceColor(R.attr.colorOnSurface), 31) ) } -fun TextView.setTextColorRes(@ColorRes id: Int) { - setTextColor(context.contextCompatColor(id)) -} - @SuppressLint("RestrictedApi") fun NavigationBarView.getItemView(@IdRes id: Int): NavigationBarItemView? { val order = (menu as MenuBuilder).findItemIndex(id) @@ -387,6 +382,27 @@ inline fun View.popupMenu( return popup } +fun MaterialCardView.makeShapeCorners( + @Dimension topStart: Float = 0f, + @Dimension bottomEnd: Float = 0f +): ShapeAppearanceModel { + return shapeAppearanceModel.toBuilder() + .apply { + if (context.resources.isLTR) { + setTopLeftCorner(CornerFamily.ROUNDED, topStart) + setBottomLeftCorner(CornerFamily.ROUNDED, if (topStart > 0) 4f.dpToPx else 0f) + setBottomRightCorner(CornerFamily.ROUNDED, bottomEnd) + setTopRightCorner(CornerFamily.ROUNDED, if (bottomEnd > 0) 4f.dpToPx else 0f) + } else { + setTopLeftCorner(CornerFamily.ROUNDED, if (topStart > 0) 4f.dpToPx else 0f) + setBottomLeftCorner(CornerFamily.ROUNDED, topStart) + setBottomRightCorner(CornerFamily.ROUNDED, if (bottomEnd > 0) 4f.dpToPx else 0f) + setTopRightCorner(CornerFamily.ROUNDED, bottomEnd) + } + } + .build() +} + fun Dialog.blurBehindWindow( window: Window?, blurAmount: Float = 20f, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/E2EBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/E2EBottomSheetDialog.kt index f4cfe9b5c4..0b1b8ba6b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/E2EBottomSheetDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/E2EBottomSheetDialog.kt @@ -1,28 +1,27 @@ package eu.kanade.tachiyomi.widget import android.app.Activity -import android.os.Build import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.updateLayoutParams +import androidx.core.view.WindowInsetsControllerCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -import eu.kanade.tachiyomi.R /** * Edge to Edge BottomSheetDiolag that uses a custom theme and settings to extend pass the nav bar */ @Suppress("LeakingThis") abstract class E2EBottomSheetDialog(activity: Activity) : - BottomSheetDialog(activity, R.style.BottomSheetDialogTheme) { + BottomSheetDialog(activity) { protected val binding: VB protected val sheetBehavior: BottomSheetBehavior<*> protected open var recyclerView: RecyclerView? = null + private val isLight: Boolean init { binding = createBinding(activity.layoutInflater) setContentView(binding.root) @@ -31,21 +30,17 @@ abstract class E2EBottomSheetDialog(activity: Activity) : val contentView = binding.root - window?.navigationBarColor = activity.window.navigationBarColor - val isLight = (activity.window?.decorView?.systemUiVisibility ?: 0) and View - .SYSTEM_UI_FLAG_LIGHT_STATUS_BAR == View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isLight) { - window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + val aWic = WindowInsetsControllerCompat(activity.window, activity.window.decorView) + isLight = aWic.isAppearanceLightStatusBars + window?.let { window -> + val wic = WindowInsetsControllerCompat(window, binding.root) + window.navigationBarColor = activity.window.navigationBarColor + wic.isAppearanceLightNavigationBars = isLight } - val insets = activity.window.decorView.rootWindowInsets (contentView.parent as View).background = null contentView.post { (contentView.parent as View).background = null } - contentView.updateLayoutParams { - leftMargin = insets.systemWindowInsetLeft - rightMargin = insets.systemWindowInsetRight - } contentView.requestLayout() } @@ -67,5 +62,13 @@ abstract class E2EBottomSheetDialog(activity: Activity) : } } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + window?.let { window -> + val wic = WindowInsetsControllerCompat(window, binding.root) + wic.isAppearanceLightNavigationBars = isLight + } + } + abstract fun createBinding(inflater: LayoutInflater): VB } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt index dc9b99d968..61012ed0a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt @@ -45,16 +45,12 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? binding.actionsContainer.removeAllViews() if (!actions.isNullOrEmpty()) { actions.forEach { - val button = ( - inflate( - context, - R.layout.material_text_button, - null - ) as MaterialButton - ).apply { - setText(it.resId) - setOnClickListener(it.listener) - } + val button = + (inflate(context, R.layout.material_text_button, null) as MaterialButton) + .apply { + setText(it.resId) + setOnClickListener(it.listener) + } binding.actionsContainer.addView(button) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt deleted file mode 100644 index a0c8242b56..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt +++ /dev/null @@ -1,73 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.content.Context -import android.os.Parcelable -import android.util.AttributeSet -import android.widget.SeekBar -import eu.kanade.tachiyomi.R -import kotlin.math.abs - -class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - SeekBar(context, attrs) { - - private var minValue: Int = 0 - private var maxValue: Int = 0 - private var listener: OnSeekBarChangeListener? = null - - init { - val styledAttributes = context.obtainStyledAttributes( - attrs, - R.styleable.NegativeSeekBar, - 0, - 0 - ) - - try { - setMinSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_min_seek, 0)) - setMaxSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_max_seek, 0)) - } finally { - styledAttributes.recycle() - } - - super.setOnSeekBarChangeListener( - object : OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) { - listener?.onProgressChanged(seekBar, minValue + value, fromUser) - } - - override fun onStartTrackingTouch(p0: SeekBar?) { - listener?.onStartTrackingTouch(p0) - } - - override fun onStopTrackingTouch(p0: SeekBar?) { - listener?.onStopTrackingTouch(p0) - } - } - ) - } - - override fun setProgress(progress: Int) { - super.setProgress(abs(minValue) + progress) - } - - fun setMinSeek(minValue: Int) { - this.minValue = minValue - max = (this.maxValue - this.minValue) - } - - fun setMaxSeek(maxValue: Int) { - this.maxValue = maxValue - max = (this.maxValue - this.minValue) - } - - override fun setOnSeekBarChangeListener(listener: OnSeekBarChangeListener?) { - this.listener = listener - } - - override fun onRestoreInstanceState(state: Parcelable?) { - // We can't restore the progress from the saved state because it gets shifted. - val origProgress = progress - super.onRestoreInstanceState(state) - super.setProgress(origProgress) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt deleted file mode 100644 index 12e903bfb1..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.widget -import android.widget.SeekBar - -open class SimpleSeekBarListener : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - } - - override fun onStartTrackingTouch(seekBar: SeekBar) { - } - - override fun onStopTrackingTouch(seekBar: SeekBar) { - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TabbedBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TabbedBottomSheet.kt index 9076427ee9..c4f7a3ecd8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TabbedBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TabbedBottomSheet.kt @@ -95,11 +95,7 @@ class MeasuredViewPager @JvmOverloads constructor(context: Context, attrs: Attri super.onMeasure(widthMeasureSpec, heightSpec) var height = 0 val childWidthSpec = MeasureSpec.makeMeasureSpec( - max( - 0, - MeasureSpec.getSize(widthMeasureSpec) - - paddingLeft - paddingRight - ), + max(0, MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight), MeasureSpec.getMode(widthMeasureSpec) ) for (i in 0 until childCount) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt index 13601c95e4..87487cee46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt @@ -2,19 +2,38 @@ package eu.kanade.tachiyomi.widget import android.content.Context import android.content.res.ColorStateList +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.core.graphics.ColorUtils import androidx.core.view.updateLayoutParams import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.TriStateCheckBoxBinding import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.setAnimVectorCompat import eu.kanade.tachiyomi.util.view.setVectorCompat +import kotlin.math.roundToInt class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + var useIndeterminateForInverse: Boolean = false + set(value) { + field = value + if (if (field) state == State.INVERSED else state == State.INDETERMINATE) { + state = if (!field) State.INVERSED else State.INDETERMINATE + } + } + + var skipInversed: Boolean = false + set(value) { + field = value + if (field && (state == State.INVERSED || state == State.INDETERMINATE)) { + state = State.UNCHECKED + } + } + var text: CharSequence get() { return binding.textView.text @@ -46,7 +65,21 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : this, false ) + + private val disabledAlpha = run { + val typedArray = context.obtainStyledAttributes(intArrayOf(android.R.attr.disabledAlpha)) + val attrValue = typedArray.getFloat(0, 0f) + typedArray.recycle() + attrValue + } private var mOnCheckedChangeListener: OnCheckedChangeListener? = null + private val uncheckedColor = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal)) + private val checkedColor = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondary)) + private val inverseColor = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondaryVariant)) + private val indeterColor = ColorStateList.valueOf(context.getResourceColor(R.attr.colorPrimary)) + private val disabledColor = ColorStateList.valueOf( + ColorUtils.setAlphaComponent(context.getResourceColor(R.attr.colorControlNormal), (disabledAlpha * 255).roundToInt()) + ) init { addView(binding.root) @@ -63,6 +96,11 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : binding.textView.setTextAppearance(resourceId) } + val textColor = a.getColor(R.styleable.TriStateCheckBox_android_textColor, 0) + if (textColor != 0) { + binding.textView.setTextColor(textColor) + } + val drawablePadding = a.getDimensionPixelSize(R.styleable.TriStateCheckBox_android_drawablePadding, 0) if (drawablePadding != 0) { binding.textView.updateLayoutParams { @@ -70,19 +108,41 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : } } - a.recycle() - setOnClickListener { - setState( - when (state) { - State.CHECKED -> State.INVERSED - State.UNCHECKED -> State.CHECKED - else -> State.UNCHECKED - }, - true - ) + goToNextStep() mOnCheckedChangeListener?.onCheckedChanged(this, state) } + isClickable = a.getBoolean(R.styleable.TriStateCheckBox_android_clickable, true) + isFocusable = a.getBoolean(R.styleable.TriStateCheckBox_android_focusable, true) + + a.recycle() + } + + fun goToNextStep() { + setState( + when (state) { + State.CHECKED -> when { + skipInversed -> State.UNCHECKED + useIndeterminateForInverse -> State.INDETERMINATE + else -> State.INVERSED + } + State.UNCHECKED -> State.CHECKED + else -> State.UNCHECKED + }, + true + ) + } + + override fun setEnabled(enabled: Boolean) { + super.setEnabled(enabled) + if (enabled) { + binding.textView.alpha = 1f + updateDrawable() + } else { + binding.textView.alpha = disabledAlpha + binding.triStateBox.imageTintList = disabledColor + binding.triStateBox.backgroundTintList = disabledColor + } } fun setState(state: State, animated: Boolean = false) { @@ -103,7 +163,7 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : mOnCheckedChangeListener = listener } - fun animateDrawableToState(state: State) { + private fun animateDrawableToState(state: State) { val oldState = this.state if (state == oldState) return this.state = state @@ -112,53 +172,60 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : State.UNCHECKED -> { setAnimVectorCompat( when (oldState) { + State.INDETERMINATE -> R.drawable.anim_checkbox_indeterminate_to_blank_24dp State.INVERSED -> R.drawable.anim_check_box_x_to_blank_24dp else -> R.drawable.anim_check_box_checked_to_blank_24dp - }, - R.attr.colorControlNormal + } ) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal)) + backgroundTintList = uncheckedColor } State.CHECKED -> { - setAnimVectorCompat( - R.drawable.anim_check_box_blank_to_checked_24dp, - R.attr.colorSecondary - ) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondary)) + setAnimVectorCompat(R.drawable.anim_check_box_blank_to_checked_24dp) + backgroundTintList = checkedColor } State.INVERSED -> { setAnimVectorCompat( - R.drawable.anim_check_box_checked_to_x_24dp, - R.attr.colorSecondaryVariant + when (oldState) { + State.CHECKED -> R.drawable.anim_check_box_checked_to_x_24dp + else -> R.drawable.anim_checkbox_blank_to_x_24dp + } ) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondaryVariant)) + backgroundTintList = inverseColor + } + State.INDETERMINATE -> { + setAnimVectorCompat(R.drawable.anim_check_box_checked_to_indeterminate_24dp) + backgroundTintList = indeterColor } } + if (this@TriStateCheckBox.isEnabled) imageTintList = backgroundTintList } } - fun updateDrawable() { + fun setCheckboxBackground(drawable: Drawable?) { + binding.triStateBox.background = drawable + } + + private fun updateDrawable() { with(binding.triStateBox) { - when (state) { + backgroundTintList = when (state) { State.UNCHECKED -> { - setVectorCompat( - R.drawable.ic_check_box_outline_blank_24dp, - R.attr.colorControlNormal - ) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal)) + setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp) + uncheckedColor } State.CHECKED -> { - setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorSecondary) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondary)) + setVectorCompat(R.drawable.ic_check_box_24dp) + checkedColor } State.INVERSED -> { - setVectorCompat( - R.drawable.ic_check_box_x_24dp, - R.attr.colorSecondaryVariant - ) - backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorSecondaryVariant)) + setVectorCompat(R.drawable.ic_check_box_x_24dp) + inverseColor + } + State.INDETERMINATE -> { + setVectorCompat(R.drawable.ic_check_box_indeterminate_24dp) + indeterColor } } + if (this@TriStateCheckBox.isEnabled) imageTintList = backgroundTintList } } @@ -166,7 +233,7 @@ class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : UNCHECKED, CHECKED, INVERSED, - ; + INDETERMINATE, } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/CustomDialogTitle.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/CustomDialogTitle.kt new file mode 100644 index 0000000000..5b014aeb1a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/CustomDialogTitle.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.widget.materialdialogs + +import android.content.Context +import android.text.Layout +import android.util.AttributeSet +import android.util.TypedValue +import com.google.android.material.textview.MaterialTextView +import eu.kanade.tachiyomi.R + +class CustomDialogTitle constructor(context: Context, attrs: AttributeSet? = null) : + MaterialTextView(context, attrs) { + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val layout: Layout? = layout + if (layout != null) { + val lineCount = layout.lineCount + if (lineCount > 0) { + val ellipsisCount = layout.getEllipsisCount(lineCount - 1) + if (ellipsisCount > 0) { + isSingleLine = false + maxLines = 2 + val a = context.obtainStyledAttributes( + null, + R.styleable.TextAppearance, + android.R.attr.textAppearanceMedium, + android.R.style.TextAppearance_Medium + ) + a.getDimensionPixelSize( + R.styleable.TextAppearance_android_textSize, + 0 + ).takeIf { it != 0 }?.let { textSize -> + // textSize is already expressed in pixels + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize.toFloat()) + } + a.recycle() + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt deleted file mode 100644 index 3201145a5f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.widget.materialdialogs - -import androidx.annotation.CheckResult -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.customListAdapter - -/** - * A variant of listItemsMultiChoice that allows for checkboxes that supports 4 states instead. - */ -@CheckResult -fun MaterialDialog.listItemsQuadStateMultiChoice( - items: List, - disabledIndices: IntArray? = null, - initialSelection: IntArray = IntArray(items.size), - selection: QuadStateMultiChoiceListener -): MaterialDialog { - return customListAdapter( - QuadStateMultiChoiceDialogAdapter( - dialog = this, - items = items, - disabledItems = disabledIndices, - initialSelection = initialSelection, - selection = selection - ) - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt deleted file mode 100644 index d530d93d13..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt +++ /dev/null @@ -1,48 +0,0 @@ -package eu.kanade.tachiyomi.widget.materialdialogs - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatImageView -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.view.setAnimVectorCompat -import eu.kanade.tachiyomi.util.view.setVectorCompat - -class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - AppCompatImageView(context, attrs) { - - var state: State = State.UNCHECKED - - fun animateDrawableToState(state: State) { - if (state == this.state) return - when (state) { - State.UNCHECKED -> setAnimVectorCompat( - when (this.state) { - State.INVERSED -> R.drawable.anim_check_box_x_to_blank_24dp - else -> R.drawable.anim_check_box_checked_to_blank_24dp - }, - R.attr.colorControlNormal - ) - State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorSecondary) - State.CHECKED -> setAnimVectorCompat(R.drawable.anim_check_box_blank_to_checked_24dp, R.attr.colorSecondary) - State.INVERSED -> setAnimVectorCompat(R.drawable.anim_check_box_checked_to_x_24dp, R.attr.colorOnSurface) - } - this.state = state - } - - fun updateDrawable() { - when (state) { - State.UNCHECKED -> setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal) - State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorSecondary) - State.CHECKED -> setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorSecondary) - State.INVERSED -> setVectorCompat(R.drawable.ic_check_box_x_24dp, R.attr.colorOnSurface) - } - } - - enum class State { - UNCHECKED, - INDETERMINATE, - CHECKED, - INVERSED, - ; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt deleted file mode 100644 index f3a9f11fdb..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt +++ /dev/null @@ -1,192 +0,0 @@ -package eu.kanade.tachiyomi.widget.materialdialogs - -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.internal.list.DialogAdapter -import com.afollestad.materialdialogs.list.getItemSelector -import com.afollestad.materialdialogs.utils.MDUtil.inflate -import com.afollestad.materialdialogs.utils.MDUtil.maybeSetTextColor -import eu.kanade.tachiyomi.R - -private object CheckPayload -private object InverseCheckPayload -private object UncheckPayload - -typealias QuadStateMultiChoiceListener = ((dialog: MaterialDialog, indices: IntArray, items: List) -> Unit)? - -internal class QuadStateMultiChoiceDialogAdapter( - private var dialog: MaterialDialog, - internal var items: List, - disabledItems: IntArray?, - initialSelection: IntArray, - internal var selection: QuadStateMultiChoiceListener -) : RecyclerView.Adapter(), - DialogAdapter { - - private val states = QuadStateCheckBox.State.values() - - private var currentSelection: IntArray = initialSelection - set(value) { - val previousSelection = field - field = value - previousSelection.forEachIndexed { index, previous -> - val current = value[index] - when { - current == QuadStateCheckBox.State.CHECKED.ordinal && previous != QuadStateCheckBox.State.CHECKED.ordinal -> { - // This value was selected - notifyItemChanged(index, CheckPayload) - } - current == QuadStateCheckBox.State.INVERSED.ordinal && previous != QuadStateCheckBox.State.INVERSED.ordinal -> { - // This value was inverse selected - notifyItemChanged(index, InverseCheckPayload) - } - current == QuadStateCheckBox.State.UNCHECKED.ordinal && previous != QuadStateCheckBox.State.UNCHECKED.ordinal -> { - // This value was unselected - notifyItemChanged(index, UncheckPayload) - } - } - } - } - private var disabledIndices: IntArray = disabledItems ?: IntArray(0) - - internal fun itemClicked(index: Int) { - val newSelection = this.currentSelection.toMutableList() - newSelection[index] = when (currentSelection[index]) { - QuadStateCheckBox.State.CHECKED.ordinal -> QuadStateCheckBox.State.INVERSED.ordinal - QuadStateCheckBox.State.INVERSED.ordinal -> QuadStateCheckBox.State.UNCHECKED.ordinal - // INDETERMINATE or UNCHECKED - else -> QuadStateCheckBox.State.CHECKED.ordinal - } - currentSelection = newSelection.toIntArray() - val selectedItems = this.items.filterIndexed { index, _ -> - currentSelection[index] != 0 - } - selection?.invoke(dialog, currentSelection, selectedItems) - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): QuadStateMultiChoiceViewHolder { - val listItemView: View = parent.inflate(dialog.windowContext, R.layout.md_listitem_quadstatemultichoice) - val viewHolder = QuadStateMultiChoiceViewHolder( - itemView = listItemView, - adapter = this - ) - viewHolder.titleView.maybeSetTextColor(dialog.windowContext, R.attr.md_color_content) - - return viewHolder - } - - override fun getItemCount() = items.size - - override fun onBindViewHolder( - holder: QuadStateMultiChoiceViewHolder, - position: Int - ) { - holder.isEnabled = !disabledIndices.contains(position) - - holder.controlView.state = states.getOrNull(currentSelection[position]) ?: QuadStateCheckBox.State.UNCHECKED - holder.controlView.updateDrawable() - holder.titleView.text = items[position] - holder.itemView.background = dialog.getItemSelector() - - if (dialog.bodyFont != null) { - holder.titleView.typeface = dialog.bodyFont - } - } - - override fun onBindViewHolder( - holder: QuadStateMultiChoiceViewHolder, - position: Int, - payloads: MutableList - ) { - when (payloads.firstOrNull()) { - CheckPayload -> { - holder.controlView.animateDrawableToState(QuadStateCheckBox.State.CHECKED) - return - } - InverseCheckPayload -> { - holder.controlView.animateDrawableToState(QuadStateCheckBox.State.INVERSED) - return - } - UncheckPayload -> { - holder.controlView.animateDrawableToState(QuadStateCheckBox.State.UNCHECKED) - return - } - } - super.onBindViewHolder(holder, position, payloads) - } - - override fun positiveButtonClicked() { -// selection.invoke(currentSelection) - } - - override fun replaceItems( - items: List, - listener: QuadStateMultiChoiceListener? - ) { - this.items = items - if (listener != null) { - this.selection = listener - } - this.notifyDataSetChanged() - } - - override fun disableItems(indices: IntArray) { - this.disabledIndices = indices - notifyDataSetChanged() - } - - override fun checkItems(indices: IntArray) { - val newSelection = this.currentSelection.toMutableList() - for (index in indices) { - newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal - } - this.currentSelection = newSelection.toIntArray() - } - - override fun uncheckItems(indices: IntArray) { - val newSelection = this.currentSelection.toMutableList() - for (index in indices) { - newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal - } - this.currentSelection = newSelection.toIntArray() - } - - override fun toggleItems(indices: IntArray) { - val newSelection = this.currentSelection.toMutableList() - for (index in indices) { - if (this.disabledIndices.contains(index)) { - continue - } - - if (this.currentSelection[index] != QuadStateCheckBox.State.CHECKED.ordinal) { - newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal - } else { - newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal - } - } - this.currentSelection = newSelection.toIntArray() - } - - override fun checkAllItems() { - this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.CHECKED.ordinal } - } - - override fun uncheckAllItems() { - this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.UNCHECKED.ordinal } - } - - override fun toggleAllChecked() { - if (this.currentSelection.any { it != QuadStateCheckBox.State.CHECKED.ordinal }) { - checkAllItems() - } else { - uncheckAllItems() - } - } - - override fun isItemChecked(index: Int) = this.currentSelection[index] == QuadStateCheckBox.State.CHECKED.ordinal -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceDialogAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceDialogAdapter.kt new file mode 100644 index 0000000000..daa1ac6c92 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceDialogAdapter.kt @@ -0,0 +1,145 @@ +package eu.kanade.tachiyomi.widget.materialdialogs + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import eu.kanade.tachiyomi.databinding.ListitemTristatechoiceBinding +import eu.kanade.tachiyomi.widget.TriStateCheckBox + +private object CheckPayload +private object InverseCheckPayload +private object UncheckPayload + +internal typealias TriStateMultiChoiceListener = ( + adapter: TriStateMultiChoiceDialogAdapter, + indices: IntArray, + items: List, + selectedIndex: Int, + selectedState: Int +) -> Unit + +internal class TriStateMultiChoiceDialogAdapter( + private var dialog: MaterialAlertDialogBuilder, + internal var items: List, + disabledItems: IntArray?, + initialSelection: IntArray, + private val skipChecked: Boolean = false, + internal var listener: TriStateMultiChoiceListener?, +) : RecyclerView.Adapter() { + + private val states = TriStateCheckBox.State.values() + private val defaultOrdinal + get() = if (skipChecked) { + TriStateCheckBox.State.INVERSED.ordinal + } else { + TriStateCheckBox.State.CHECKED.ordinal + } + + private var currentSelection: IntArray = initialSelection + set(value) { + val previousSelection = field + field = value + previousSelection.forEachIndexed { index, previous -> + val current = value[index] + when { + current == TriStateCheckBox.State.CHECKED.ordinal && previous != TriStateCheckBox.State.CHECKED.ordinal -> { + // This value was selected + notifyItemChanged(index, CheckPayload) + } + current == TriStateCheckBox.State.INVERSED.ordinal && previous != TriStateCheckBox.State.INVERSED.ordinal -> { + // This value was inverse selected + notifyItemChanged(index, InverseCheckPayload) + } + current == TriStateCheckBox.State.UNCHECKED.ordinal && previous != TriStateCheckBox.State.UNCHECKED.ordinal -> { + // This value was unselected + notifyItemChanged(index, UncheckPayload) + } + } + } + } + private var disabledIndices: IntArray = disabledItems ?: IntArray(0) + + internal fun itemClicked(index: Int) { + val newSelection = this.currentSelection.toMutableList() + newSelection[index] = when (currentSelection[index]) { + TriStateCheckBox.State.CHECKED.ordinal -> TriStateCheckBox.State.INVERSED.ordinal + TriStateCheckBox.State.INVERSED.ordinal -> TriStateCheckBox.State.UNCHECKED.ordinal + // UNCHECKED + else -> defaultOrdinal + } + currentSelection = newSelection.toIntArray() + val selectedItems = this.items.filterIndexed { i, _ -> + currentSelection[i] != 0 + } + listener?.invoke(this, currentSelection, selectedItems, index, newSelection[index]) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): TriStateMultiChoiceViewHolder { + val listItemView: View = ListitemTristatechoiceBinding + .inflate(LayoutInflater.from(dialog.context), parent, false).root + return TriStateMultiChoiceViewHolder( + itemView = listItemView, + adapter = this + ) + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder( + holder: TriStateMultiChoiceViewHolder, + position: Int + ) { + holder.isEnabled = !disabledIndices.contains(position) + + holder.controlView.state = states.getOrNull(currentSelection[position]) ?: TriStateCheckBox.State.UNCHECKED + holder.controlView.text = items[position] +// holder.itemView.background = dialog.getItemSelector() + +// if (dialog.bodyFont != null) { +// holder.titleView.typeface = dialog.bodyFont +// } + } + + override fun onBindViewHolder( + holder: TriStateMultiChoiceViewHolder, + position: Int, + payloads: MutableList + ) { + when (payloads.firstOrNull()) { + CheckPayload -> { + holder.controlView.setState(TriStateCheckBox.State.CHECKED, true) + return + } + InverseCheckPayload -> { + holder.controlView.setState(TriStateCheckBox.State.INVERSED, true) + return + } + UncheckPayload -> { + holder.controlView.setState(TriStateCheckBox.State.UNCHECKED, true) + return + } + } + super.onBindViewHolder(holder, position, payloads) + } + + fun checkItems(indices: IntArray) { + val newSelection = this.currentSelection.toMutableList() + for (index in indices) { + newSelection[index] = defaultOrdinal + } + this.currentSelection = newSelection.toIntArray() + } + + fun uncheckItems(indices: IntArray) { + val newSelection = this.currentSelection.toMutableList() + for (index in indices) { + newSelection[index] = TriStateCheckBox.State.UNCHECKED.ordinal + } + this.currentSelection = newSelection.toIntArray() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceViewHolder.kt similarity index 54% rename from app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceViewHolder.kt rename to app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceViewHolder.kt index 27f0133b51..26e370499d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/TriStateMultiChoiceViewHolder.kt @@ -1,29 +1,28 @@ package eu.kanade.tachiyomi.widget.materialdialogs import android.view.View -import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.widget.TriStateCheckBox -internal class QuadStateMultiChoiceViewHolder( +internal class TriStateMultiChoiceViewHolder( itemView: View, - private val adapter: QuadStateMultiChoiceDialogAdapter + private val adapter: TriStateMultiChoiceDialogAdapter ) : RecyclerView.ViewHolder(itemView), View.OnClickListener { + val controlView: TriStateCheckBox = itemView.findViewById(R.id.md_tri_state_checkbox) + init { itemView.setOnClickListener(this) + controlView.isClickable = false + controlView.isFocusable = false + controlView.setCheckboxBackground(null) } - val controlView: QuadStateCheckBox = itemView.findViewById(R.id.md_quad_state_control) - val titleView: TextView = itemView.findViewById(R.id.md_quad_state_title) - var isEnabled: Boolean get() = itemView.isEnabled set(value) { itemView.isEnabled = value controlView.isEnabled = value - titleView.isEnabled = value - titleView.alpha = if (value) 1f else 0.75f - controlView.alpha = if (value) 1f else 0.75f } override fun onClick(view: View) = adapter.itemClicked(bindingAdapterPosition) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt index 3dcab32510..2cae271f03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt @@ -3,8 +3,7 @@ package eu.kanade.tachiyomi.widget.preference import android.app.Activity import android.content.Context import android.util.AttributeSet -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice +import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.tachiyomi.data.preference.getOrDefault class IntListMatPreference @JvmOverloads constructor( @@ -37,23 +36,26 @@ class IntListMatPreference @JvmOverloads constructor( defValue = defaultValue as? Int ?: defValue } - override fun dialog(): MaterialDialog { + override fun dialog(): MaterialAlertDialogBuilder { return super.dialog().apply { val default = entryValues.indexOf(customSelectedValue ?: prefs.getInt(key, defValue).getOrDefault()) - listItemsSingleChoice( - items = entries, - waitForPositiveButton = false, - initialSelection = default - ) { - _, pos, _ -> + setSingleChoiceItems(entries.toTypedArray(), default) { dialog, pos -> val value = entryValues[pos] if (key != null) { prefs.getInt(key, defValue).set(value) } callChangeListener(value) notifyChanged() - dismiss() + dialog.dismiss() } +// listItemsSingleChoice( +// items = entries, +// waitForPositiveButton = false, +// initialSelection = default +// ) { +// _, pos, _ -> +// +// } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt deleted file mode 100644 index 63b52eb2a9..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.widget.preference - -import android.content.Context -import android.util.AttributeSet -import androidx.preference.ListPreference - -class IntListPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - ListPreference(context, attrs) { - - override fun persistString(value: String?): Boolean { - return value != null && persistInt(value.toInt()) - } - - override fun getPersistedString(defaultReturnValue: String?): String? { - // When the underlying preference is using a PreferenceDataStore, there's no way (for now) - // to check if a value is in the store, so we use a most likely unused value as workaround - val defaultIntValue = Int.MIN_VALUE + 1 - - val value = getPersistedInt(defaultIntValue) - return if (value != defaultIntValue) { - value.toString() - } else { - defaultReturnValue - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt index e03c5f4c64..b33c4562f1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt @@ -5,8 +5,7 @@ import android.app.Activity import android.content.Context import android.util.AttributeSet import androidx.preference.Preference -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice +import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.tachiyomi.data.preference.getOrDefault open class ListMatPreference @JvmOverloads constructor( @@ -37,25 +36,21 @@ open class ListMatPreference @JvmOverloads constructor( else entries[index] } - override fun dialog(): MaterialDialog { + override fun dialog(): MaterialAlertDialogBuilder { return super.dialog().apply { - setItems() + setListItems() } } @SuppressLint("CheckResult") - open fun MaterialDialog.setItems() { + open fun MaterialAlertDialogBuilder.setListItems() { val default = entryValues.indexOf( if (sharedPref != null) { val settings = context.getSharedPreferences(sharedPref, Context.MODE_PRIVATE) settings.getString(key, "") } else prefs.getStringPref(key, defValue).getOrDefault() ) - listItemsSingleChoice( - items = entries, - waitForPositiveButton = false, - initialSelection = default - ) { _, pos, _ -> + setSingleChoiceItems(entries.toTypedArray(), default) { dialog, pos -> val value = entryValues[pos] if (sharedPref != null) { val oldDef = if (default > -1) entries[default] else "" @@ -75,7 +70,7 @@ open class ListMatPreference @JvmOverloads constructor( this@ListMatPreference.summary = this@ListMatPreference.summary callChangeListener(value) } - dismiss() + dialog.dismiss() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt index 78bc241c90..a7f2154057 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt @@ -4,15 +4,12 @@ import android.app.Dialog import android.os.Bundle import android.view.View import androidx.annotation.StringRes -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.customview.getCustomView import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.PrefAccountLoginBinding import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.util.system.materialAlertDialog import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -39,14 +36,13 @@ abstract class LoginDialogPreference( open var canLogout = false override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val dialog = MaterialDialog(activity!!).apply { - customView(R.layout.pref_account_login, scrollable = false) + binding = PrefAccountLoginBinding.inflate(activity!!.layoutInflater) + val dialog = activity!!.materialAlertDialog().apply { + setView(binding.root) } - binding = PrefAccountLoginBinding.bind(dialog.getCustomView()) + onViewCreated(binding.root) - onViewCreated(dialog.view) - - return dialog + return dialog.create() } fun onViewCreated(view: View) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt index bd3cf315e9..28b0410111 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt @@ -4,10 +4,11 @@ import android.app.Activity import android.content.Context import android.util.AttributeSet import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog import androidx.preference.Preference -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.callbacks.onDismiss +import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.util.system.materialAlertDialog import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -33,13 +34,19 @@ open class MatPreference @JvmOverloads constructor( override fun onClick() { if (!isShowing) { - dialog().apply { - onDismiss { this@MatPreference.isShowing = false } - }.show() + val dialog = dialog().apply { + setOnDismissListener { this@MatPreference.isShowing = false } + }.create() +// dialog.setOnShowListener { + onShow(dialog) +// } + dialog.show() } isShowing = true } + protected open fun onShow(dialog: AlertDialog) { } + protected open var customSummaryProvider: SummaryProvider? = null set(value) { field = value @@ -80,14 +87,14 @@ open class MatPreference @JvmOverloads constructor( super.setSummary(summary) } - open fun dialog(): MaterialDialog { - return MaterialDialog(activity ?: context).apply { + open fun dialog(): MaterialAlertDialogBuilder { + return (activity ?: context).materialAlertDialog().apply { if (dialogTitleRes != null) { - title(res = dialogTitleRes) + setTitle(dialogTitleRes!!) } else if (title != null) { - title(text = title.toString()) + setTitle(title.toString()) } - negativeButton(android.R.string.cancel) + setNegativeButton(android.R.string.cancel, null) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt index f9b69f9d3d..0094ad04a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt @@ -4,13 +4,12 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.util.AttributeSet -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.checkItem -import com.afollestad.materialdialogs.list.isItemChecked -import com.afollestad.materialdialogs.list.listItemsMultiChoice -import com.afollestad.materialdialogs.list.uncheckItem +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatCheckedTextView +import androidx.core.view.children +import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.ui.setting.defaultValue +import eu.kanade.tachiyomi.util.system.disableItems class MultiListMatPreference @JvmOverloads constructor( activity: Activity?, @@ -64,43 +63,60 @@ class MultiListMatPreference @JvmOverloads constructor( } @SuppressLint("CheckResult") - override fun MaterialDialog.setItems() { + override fun MaterialAlertDialogBuilder.setListItems() { val set = prefs.getStringSet(key, defValue).getOrDefault() - var default = set.mapNotNull { - if (entryValues.indexOf(it) == -1) null - else entryValues.indexOf(it) + if (allSelectionRes != null && !showAllLast) 1 else 0 - } - .toIntArray() val items = if (allSelectionRes != null) { if (showAllLast) entries + listOf(context.getString(allSelectionRes!!)) else listOf(context.getString(allSelectionRes!!)) + entries } else entries val allPos = if (showAllLast) items.size - 1 else 0 - if (allSelectionRes != null && default.isEmpty()) default = intArrayOf(allPos) - else if (allSelectionRes != null && allIsAlwaysSelected) default += allPos - positiveButton(android.R.string.ok) { + + val allValue = booleanArrayOf(set.isEmpty() || allIsAlwaysSelected) + val selected = + if (allSelectionRes != null && !showAllLast) { allValue } else { booleanArrayOf() } + + entryValues.map { it in set }.toBooleanArray() + + if (allSelectionRes != null && showAllLast) { allValue } else { booleanArrayOf() } + setPositiveButton(android.R.string.ok) { dialog, _ -> val pos = mutableListOf() for (i in items.indices) - if (!(allSelectionRes != null && i == allPos) && isItemChecked(i)) pos.add(i) + if (!(allSelectionRes != null && i == allPos) && selected[i]) pos.add(i) var value = pos.mapNotNull { entryValues.getOrNull(it - if (allSelectionRes != null && !showAllLast) 1 else 0) }.toSet() - if (allSelectionRes != null && !allIsAlwaysSelected && isItemChecked(0)) value = emptySet() + if (allSelectionRes != null && !allIsAlwaysSelected && selected[allPos]) value = emptySet() prefs.getStringSet(key, emptySet()).set(value) callChangeListener(value) notifyChanged() } - listItemsMultiChoice( - items = items, - allowEmptySelection = true, - disabledIndices = if (allSelectionRes != null) intArrayOf(allPos) else null, - waitForPositiveButton = false, - initialSelection = default - ) { _, pos, _ -> + setMultiChoiceItems(items.toTypedArray(), selected) { dialog, pos, checked -> + // The extra changes above sometimes don't work so theres this too + if (allSelectionRes != null && pos == allPos) { + val listView = (dialog as? AlertDialog)?.listView ?: return@setMultiChoiceItems + listView.setItemChecked(pos, !checked) + listView.children.forEach { + val cText = (it as? AppCompatCheckedTextView)?.text ?: return@forEach + val cItemIndex: Int = items.indexOf(cText) + if (cItemIndex == allPos) { + it.setOnClickListener(null) + it.isEnabled = false + return@setMultiChoiceItems + } + } + return@setMultiChoiceItems + } + selected[pos] = checked if (allSelectionRes != null && !allIsAlwaysSelected) { - if (pos.isEmpty()) checkItem(allPos) - else uncheckItem(allPos) + if (checked) selected[allPos] = false + else if (selected.none { it }) selected[allPos] = true + (dialog as? AlertDialog)?.listView?.setItemChecked(pos, selected[allPos]) } } } + + // Extra changes to make sure the all button is disabled + override fun onShow(dialog: AlertDialog) { + if (allSelectionRes != null) { + dialog.disableItems(arrayOf(context.getString(allSelectionRes!!))) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt index 52d9b5894e..9412abcd67 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt @@ -1,9 +1,7 @@ package eu.kanade.tachiyomi.widget.preference -import android.annotation.TargetApi import android.content.Context import android.content.res.TypedArray -import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH import android.util.AttributeSet import android.view.View import android.widget.Checkable @@ -42,7 +40,6 @@ class SwitchPreferenceCategory @JvmOverloads constructor( syncSwitchView(switchView) } - @TargetApi(ICE_CREAM_SANDWICH) private fun syncSwitchView(view: View) { if (view is Checkable) { val isChecked = view.isChecked diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt index 1d3b8003ae..c779fb8c61 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.widget.preference import android.app.Dialog import android.os.Bundle -import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -19,14 +19,14 @@ class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) { override fun onCreateDialog(savedViewState: Bundle?): Dialog { val serviceName = activity!!.getString(service.nameRes()) - return MaterialDialog(activity!!) - .title(text = activity!!.getString(R.string.logout_from_, serviceName)) - .negativeButton(R.string.cancel) - .positiveButton(R.string.logout) { _ -> + return activity!!.materialAlertDialog() + .setTitle(activity!!.getString(R.string.logout_from_, serviceName)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.logout) { _, _ -> service.logout() (targetController as? Listener)?.trackLogoutDialogClosed(service) activity!!.toast(R.string.successfully_logged_out) - } + }.create() } interface Listener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TriStateListPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TriStateListPreference.kt index c64ea965a8..41d9ff3a05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TriStateListPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TriStateListPreference.kt @@ -5,13 +5,11 @@ import android.app.Activity import android.content.Context import android.util.AttributeSet import androidx.core.text.buildSpannedString -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.checkItem -import com.afollestad.materialdialogs.list.uncheckItem +import com.google.android.material.dialog.MaterialAlertDialogBuilder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox -import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice +import eu.kanade.tachiyomi.util.system.setTriStateItems +import eu.kanade.tachiyomi.widget.TriStateCheckBox class TriStateListPreference @JvmOverloads constructor( activity: Activity?, @@ -74,7 +72,7 @@ class TriStateListPreference @JvmOverloads constructor( } @SuppressLint("CheckResult") - override fun MaterialDialog.setItems() { + override fun MaterialAlertDialogBuilder.setListItems() { val set = prefs.getStringSet(key, defValue).getOrDefault() val items = if (allSelectionRes != null) { if (showAllLast) entries + listOf(context.getString(allSelectionRes!!)) @@ -85,48 +83,48 @@ class TriStateListPreference @JvmOverloads constructor( prefs.getStringSet(it, defValue).getOrDefault() }.orEmpty() val allValue = intArrayOf( - if (set.isEmpty()) QuadStateCheckBox.State.CHECKED.ordinal - else QuadStateCheckBox.State.UNCHECKED.ordinal + if (set.isEmpty()) TriStateCheckBox.State.CHECKED.ordinal + else TriStateCheckBox.State.UNCHECKED.ordinal ) val preselected = if (allSelectionRes != null && !showAllLast) { allValue } else { intArrayOf() } + entryValues .map { when (it) { - in set -> QuadStateCheckBox.State.CHECKED.ordinal - in excludedSet -> QuadStateCheckBox.State.INVERSED.ordinal - else -> QuadStateCheckBox.State.UNCHECKED.ordinal + in set -> TriStateCheckBox.State.CHECKED.ordinal + in excludedSet -> TriStateCheckBox.State.INVERSED.ordinal + else -> TriStateCheckBox.State.UNCHECKED.ordinal } } .toIntArray() + if (allSelectionRes != null && showAllLast) { allValue } else { intArrayOf() } var includedItems = set var excludedItems = excludedSet - positiveButton(android.R.string.ok) { + setPositiveButton(android.R.string.ok) { _, _ -> prefs.getStringSet(key, emptySet()).set(includedItems) excludeKey?.let { prefs.getStringSet(it, emptySet()).set(excludedItems) } callChangeListener(includedItems to excludedItems) notifyChanged() } - listItemsQuadStateMultiChoice( + setTriStateItems( items = items, disabledIndices = if (allSelectionRes != null) intArrayOf(allPos) else null, initialSelection = preselected - ) { _, sels, _ -> + ) { adapter, sels, _, _, _ -> val selections = sels.filterIndexed { index, i -> allSelectionRes == null || index != allPos } includedItems = selections - .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.CHECKED.ordinal) index else null } + .mapIndexed { index, value -> if (value == TriStateCheckBox.State.CHECKED.ordinal) index else null } .filterNotNull() .map { entryValues[it] } .toSet() excludedItems = selections - .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.INVERSED.ordinal) index else null } + .mapIndexed { index, value -> if (value == TriStateCheckBox.State.INVERSED.ordinal) index else null } .filterNotNull() .map { entryValues[it] } .toSet() if (allSelectionRes != null && !allIsAlwaysSelected) { - if (includedItems.isEmpty()) checkItem(allPos) - else uncheckItem(allPos) + if (includedItems.isEmpty()) adapter.checkItems(intArrayOf(allPos)) + else adapter.uncheckItems(intArrayOf(allPos)) } } } diff --git a/app/src/main/res/color/bottom_nav_item_selector.xml b/app/src/main/res/color/bottom_nav_item_selector.xml index 2345ab46ea..42240cea0f 100644 --- a/app/src/main/res/color/bottom_nav_item_selector.xml +++ b/app/src/main/res/color/bottom_nav_item_selector.xml @@ -1,10 +1,8 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/color/bottom_nav_text_selector.xml b/app/src/main/res/color/bottom_nav_text_selector.xml new file mode 100644 index 0000000000..7a08b172e2 --- /dev/null +++ b/app/src/main/res/color/bottom_nav_text_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/library_comfortable_grid_foreground.xml b/app/src/main/res/color/library_comfortable_grid_foreground.xml new file mode 100644 index 0000000000..86893d097c --- /dev/null +++ b/app/src/main/res/color/library_comfortable_grid_foreground.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/color/library_comfortable_subtitle_selector.xml b/app/src/main/res/color/library_comfortable_subtitle_selector.xml new file mode 100644 index 0000000000..0ba137a576 --- /dev/null +++ b/app/src/main/res/color/library_comfortable_subtitle_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/library_comfortable_title_selector.xml b/app/src/main/res/color/library_comfortable_title_selector.xml new file mode 100644 index 0000000000..29396121bd --- /dev/null +++ b/app/src/main/res/color/library_comfortable_title_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/library_grid_foreground.xml b/app/src/main/res/color/library_grid_foreground.xml index ce970cb4c8..d3c02c2ca8 100644 --- a/app/src/main/res/color/library_grid_foreground.xml +++ b/app/src/main/res/color/library_grid_foreground.xml @@ -1,5 +1,4 @@ - - + diff --git a/app/src/main/res/color/library_stroke_selector.xml b/app/src/main/res/color/library_stroke_selector.xml new file mode 100644 index 0000000000..260af2d5a8 --- /dev/null +++ b/app/src/main/res/color/library_stroke_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/ripple_nav.xml b/app/src/main/res/color/ripple_nav.xml index 869b824892..2bdcd71f9a 100644 --- a/app/src/main/res/color/ripple_nav.xml +++ b/app/src/main/res/color/ripple_nav.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/color/secondary_container.xml b/app/src/main/res/color/secondary_container.xml new file mode 100644 index 0000000000..1f25b56404 --- /dev/null +++ b/app/src/main/res/color/secondary_container.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/color/slider_active_track_color.xml b/app/src/main/res/color/slider_active_track_color.xml new file mode 100644 index 0000000000..0dc3a3357f --- /dev/null +++ b/app/src/main/res/color/slider_active_track_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/slider_inactive_track_color.xml b/app/src/main/res/color/slider_inactive_track_color.xml new file mode 100644 index 0000000000..2b07af8d68 --- /dev/null +++ b/app/src/main/res/color/slider_inactive_track_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/slider_tick_inactive.xml b/app/src/main/res/color/slider_tick_inactive.xml new file mode 100644 index 0000000000..085be465da --- /dev/null +++ b/app/src/main/res/color/slider_tick_inactive.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switch_thumb_tint.xml b/app/src/main/res/color/switch_thumb_tint.xml new file mode 100644 index 0000000000..b49bbd8518 --- /dev/null +++ b/app/src/main/res/color/switch_thumb_tint.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/color/text_btn_color_selector.xml b/app/src/main/res/color/text_btn_color_selector.xml new file mode 100644 index 0000000000..07f4a1e287 --- /dev/null +++ b/app/src/main/res/color/text_btn_color_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/action_mode_bg.xml b/app/src/main/res/drawable/action_mode_bg.xml deleted file mode 100644 index 58a1399dde..0000000000 --- a/app/src/main/res/drawable/action_mode_bg.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/anim_check_box_checked_to_indeterminate_24dp.xml b/app/src/main/res/drawable/anim_check_box_checked_to_indeterminate_24dp.xml new file mode 100644 index 0000000000..49d35aa358 --- /dev/null +++ b/app/src/main/res/drawable/anim_check_box_checked_to_indeterminate_24dp.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/anim_checkbox_blank_to_x_24dp.xml b/app/src/main/res/drawable/anim_checkbox_blank_to_x_24dp.xml new file mode 100644 index 0000000000..934c3c9dc2 --- /dev/null +++ b/app/src/main/res/drawable/anim_checkbox_blank_to_x_24dp.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/anim_checkbox_indeterminate_to_blank_24dp.xml b/app/src/main/res/drawable/anim_checkbox_indeterminate_to_blank_24dp.xml new file mode 100644 index 0000000000..2a7dca2446 --- /dev/null +++ b/app/src/main/res/drawable/anim_checkbox_indeterminate_to_blank_24dp.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/card_item_selector.xml b/app/src/main/res/drawable/card_item_selector.xml index a9d9cc7d7a..36c528f846 100644 --- a/app/src/main/res/drawable/card_item_selector.xml +++ b/app/src/main/res/drawable/card_item_selector.xml @@ -15,7 +15,7 @@ - + diff --git a/app/src/main/res/drawable/ic_check_box_indeterminate_24dp.xml b/app/src/main/res/drawable/ic_check_box_indeterminate_24dp.xml new file mode 100644 index 0000000000..6dfe095240 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_indeterminate_24dp.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/library_confortable_grid_selector.xml b/app/src/main/res/drawable/library_comfortable_grid_selector.xml similarity index 52% rename from app/src/main/res/drawable/library_confortable_grid_selector.xml rename to app/src/main/res/drawable/library_comfortable_grid_selector.xml index 940323abde..6ff7cf9d43 100644 --- a/app/src/main/res/drawable/library_confortable_grid_selector.xml +++ b/app/src/main/res/drawable/library_comfortable_grid_selector.xml @@ -2,30 +2,32 @@ + android:top="5dp" + android:left="5dp" + android:bottom="2dp" + android:right="5dp"> - - + + + android:top="5dp" + android:left="5dp" + android:bottom="2dp" + android:right="5dp"> - - + + - - + + diff --git a/app/src/main/res/drawable/library_comfortable_grid_selector_overlay.xml b/app/src/main/res/drawable/library_comfortable_grid_selector_overlay.xml new file mode 100644 index 0000000000..3f36076de4 --- /dev/null +++ b/app/src/main/res/drawable/library_comfortable_grid_selector_overlay.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/library_compact_grid_selector.xml b/app/src/main/res/drawable/library_compact_grid_selector.xml index 6c2b99fdfd..bc3544da94 100644 --- a/app/src/main/res/drawable/library_compact_grid_selector.xml +++ b/app/src/main/res/drawable/library_compact_grid_selector.xml @@ -2,32 +2,32 @@ + android:top="4dp" + android:bottom="4dp" + android:left="4dp" + android:right="4dp"> - - + + + android:top="4dp" + android:bottom="4dp" + android:left="4dp" + android:right="4dp"> - - + + - - + + diff --git a/app/src/main/res/drawable/library_compact_grid_selector_overlay.xml b/app/src/main/res/drawable/library_compact_grid_selector_overlay.xml index fe9ac1af5f..85ffaccde4 100644 --- a/app/src/main/res/drawable/library_compact_grid_selector_overlay.xml +++ b/app/src/main/res/drawable/library_compact_grid_selector_overlay.xml @@ -1,24 +1,35 @@ + android:color="?colorSecondary"> + android:top="4dp" + android:bottom="4dp" + android:left="4dp" + android:right="4dp"> - + - - - - + android:top="4dp" + android:bottom="4dp" + android:left="4dp" + android:right="4dp"> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/library_confortable_grid_selector_overlay.xml b/app/src/main/res/drawable/library_confortable_grid_selector_overlay.xml deleted file mode 100644 index 0688403b20..0000000000 --- a/app/src/main/res/drawable/library_confortable_grid_selector_overlay.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/reader_toolbar_ripple.xml b/app/src/main/res/drawable/reader_toolbar_ripple.xml index 407c8ed029..a3d9ecc39f 100644 --- a/app/src/main/res/drawable/reader_toolbar_ripple.xml +++ b/app/src/main/res/drawable/reader_toolbar_ripple.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_ripple.xml b/app/src/main/res/drawable/rounded_ripple.xml index 522b28103e..0bdb2f3ac8 100644 --- a/app/src/main/res/drawable/rounded_ripple.xml +++ b/app/src/main/res/drawable/rounded_ripple.xml @@ -1,32 +1,10 @@ + android:color="?colorControlHighlight"> - + - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_highlight_indicator.xml b/app/src/main/res/drawable/tab_highlight_indicator.xml index e4cda3a296..bbfbc07348 100644 --- a/app/src/main/res/drawable/tab_highlight_indicator.xml +++ b/app/src/main/res/drawable/tab_highlight_indicator.xml @@ -1,10 +1,10 @@ + android:end="4dp" + android:start="4dp" > - + diff --git a/app/src/main/res/layout-sw600dp-land/manga_header_item.xml b/app/src/main/res/layout-sw600dp-land/manga_header_item.xml index 70cd52a3f8..daef6efaa3 100644 --- a/app/src/main/res/layout-sw600dp-land/manga_header_item.xml +++ b/app/src/main/res/layout-sw600dp-land/manga_header_item.xml @@ -59,6 +59,8 @@ android:id="@+id/cover_card" android:layout_width="100dp" android:layout_height="wrap_content" + app:strokeWidth="0dp" + app:cardElevation="2dp" android:layout_marginStart="16dp" android:layout_marginTop="12dp" android:layout_marginBottom="16dp" @@ -83,7 +85,7 @@ @@ -221,7 +223,7 @@ @@ -221,7 +223,7 @@ + android:background="@android:color/transparent"> + android:layout_height="56dp" > - @@ -111,7 +111,7 @@ android:orientation="vertical"> - + - diff --git a/app/src/main/res/layout/bottom_menu_sheet.xml b/app/src/main/res/layout/bottom_menu_sheet.xml index cda7204e04..5e60479309 100644 --- a/app/src/main/res/layout/bottom_menu_sheet.xml +++ b/app/src/main/res/layout/bottom_menu_sheet.xml @@ -24,7 +24,7 @@ - - - diff --git a/app/src/main/res/layout/catergory_text_view.xml b/app/src/main/res/layout/catergory_text_view.xml index 7611050635..c5422a5af3 100644 --- a/app/src/main/res/layout/catergory_text_view.xml +++ b/app/src/main/res/layout/catergory_text_view.xml @@ -11,6 +11,6 @@ android:paddingTop="8dp" android:paddingEnd="0dp" android:paddingBottom="8dp" - android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" + android:textAppearance="?textAppearanceTitleMedium" android:textSize="15sp" tools:text="@string/categories" /> diff --git a/app/src/main/res/layout/chapter_filter_layout.xml b/app/src/main/res/layout/chapter_filter_layout.xml index 18e74b8e68..55d6a38bae 100644 --- a/app/src/main/res/layout/chapter_filter_layout.xml +++ b/app/src/main/res/layout/chapter_filter_layout.xml @@ -10,7 +10,7 @@ android:layout_height="wrap_content"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_quadstate.xml b/app/src/main/res/layout/dialog_quadstate.xml new file mode 100644 index 0000000000..39d651a782 --- /dev/null +++ b/app/src/main/res/layout/dialog_quadstate.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/download_bottom_sheet.xml b/app/src/main/res/layout/download_bottom_sheet.xml index f5881f6b5f..a320cec553 100644 --- a/app/src/main/res/layout/download_bottom_sheet.xml +++ b/app/src/main/res/layout/download_bottom_sheet.xml @@ -39,7 +39,7 @@ - + android:layout_height="wrap_content"> + + - + - - + + -