diff --git a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt index ba3cebc86..eb69664f7 100644 --- a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt @@ -1,6 +1,7 @@ package eu.kanade.domain.base import android.content.Context +import androidx.annotation.StringRes import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.isPreviewBuildType import eu.kanade.tachiyomi.util.system.isReleaseBuildType @@ -19,7 +20,7 @@ class BasePreferences( fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType) - enum class ExtensionInstaller(val titleResId: Int) { + enum class ExtensionInstaller(@StringRes val titleResId: Int) { LEGACY(R.string.ext_installer_legacy), PACKAGEINSTALLER(R.string.ext_installer_packageinstaller), SHIZUKU(R.string.ext_installer_shizuku), diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index c81fa1632..582dd61d7 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -30,7 +30,5 @@ class SourcePreferences( fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet()) - fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false) - fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false) } diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index 790053063..4a80724f0 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -1,8 +1,22 @@ package eu.kanade.presentation.browse +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DoneAll +import androidx.compose.material.icons.outlined.FilterList +import androidx.compose.material.icons.outlined.PushPin +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -16,19 +30,25 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter import eu.kanade.tachiyomi.util.system.LocaleHelper import tachiyomi.domain.manga.model.Manga +import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.components.material.VerticalDivider import tachiyomi.presentation.core.components.material.padding @Composable fun GlobalSearchScreen( - state: GlobalSearchState, + state: GlobalSearchScreenModel.State, + items: Map, navigateUp: () -> Unit, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, + onChangeSearchFilter: (SourceFilter) -> Unit, + onToggleResults: () -> Unit, getManga: @Composable (Manga) -> State, onClickSource: (CatalogueSource) -> Unit, onClickItem: (Manga) -> Unit, @@ -36,19 +56,80 @@ fun GlobalSearchScreen( ) { Scaffold( topBar = { scrollBehavior -> - GlobalSearchToolbar( - searchQuery = state.searchQuery, - progress = state.progress, - total = state.total, - navigateUp = navigateUp, - onChangeSearchQuery = onChangeSearchQuery, - onSearch = onSearch, - scrollBehavior = scrollBehavior, - ) + Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { + GlobalSearchToolbar( + searchQuery = state.searchQuery, + progress = state.progress, + total = state.total, + navigateUp = navigateUp, + onChangeSearchQuery = onChangeSearchQuery, + onSearch = onSearch, + scrollBehavior = scrollBehavior, + ) + + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .padding(horizontal = MaterialTheme.padding.small), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + // TODO: make this UX better; it only applies when triggering a new search + FilterChip( + selected = state.sourceFilter == SourceFilter.PinnedOnly, + onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.PushPin, + contentDescription = null, + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.pinned_sources)) + }, + ) + FilterChip( + selected = state.sourceFilter == SourceFilter.All, + onClick = { onChangeSearchFilter(SourceFilter.All) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.DoneAll, + contentDescription = null, + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.all)) + }, + ) + + VerticalDivider() + + FilterChip( + selected = state.onlyShowHasResults, + onClick = { onToggleResults() }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.FilterList, + contentDescription = null, + modifier = Modifier + .size(FilterChipDefaults.IconSize), + ) + }, + label = { + Text(text = stringResource(id = R.string.has_results)) + }, + ) + } + + Divider() + } }, ) { paddingValues -> GlobalSearchContent( - items = state.items, + items = items, contentPadding = paddingValues, getManga = getManga, onClickSource = onClickSource, diff --git a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt index d91f8fc04..1db4c0f18 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -25,10 +26,11 @@ import tachiyomi.domain.library.model.sort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem -import tachiyomi.presentation.core.components.RadioItem +import tachiyomi.presentation.core.components.SettingsFlowRow import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.TriStateItem +import tachiyomi.presentation.core.components.material.ChoiceChip @Composable fun LibrarySettingsDialog( @@ -167,23 +169,26 @@ private fun ColumnScope.SortPage( } } +private val displayModes = listOf( + R.string.action_display_grid to LibraryDisplayMode.CompactGrid, + R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid, + R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid, + R.string.action_display_list to LibraryDisplayMode.List, +) + @Composable private fun ColumnScope.DisplayPage( screenModel: LibrarySettingsScreenModel, ) { - HeadingItem(R.string.action_display_mode) val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState() - listOf( - R.string.action_display_grid to LibraryDisplayMode.CompactGrid, - R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid, - R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid, - R.string.action_display_list to LibraryDisplayMode.List, - ).map { (titleRes, mode) -> - RadioItem( - label = stringResource(titleRes), - selected = displayMode == mode, - onClick = { screenModel.setDisplayMode(mode) }, - ) + SettingsFlowRow(R.string.action_display_mode) { + displayModes.map { (titleRes, mode) -> + ChoiceChip( + isSelected = displayMode == mode, + onClick = { screenModel.setDisplayMode(mode) }, + content = { Text(stringResource(titleRes)) }, + ) + } } if (displayMode != LibraryDisplayMode.List) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 01fb87e7e..5b3ab28cf 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -29,10 +29,6 @@ object SettingsBrowseScreen : SearchableSettings { Preference.PreferenceGroup( title = stringResource(R.string.label_sources), preferenceItems = listOf( - Preference.PreferenceItem.SwitchPreference( - pref = sourcePreferences.searchPinnedSourcesOnly(), - title = stringResource(R.string.pref_search_pinned_sources_only), - ), Preference.PreferenceItem.SwitchPreference( pref = sourcePreferences.hideInLibraryItems(), title = stringResource(R.string.pref_hide_in_library_items), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 31f60acfd..0a19a72c2 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState @@ -164,9 +163,9 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = navModePref, title = stringResource(R.string.pref_viewer_nav), - entries = stringArrayResource(id = R.array.pager_nav).let { - it.indices.zip(it).toMap() - }, + entries = ReaderPreferences.TapZones + .mapIndexed { index, it -> index to stringResource(it) } + .toMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.pagerNavInverted(), @@ -182,25 +181,16 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = imageScaleTypePref, title = stringResource(R.string.pref_image_scale_type), - entries = mapOf( - 1 to stringResource(R.string.scale_type_fit_screen), - 2 to stringResource(R.string.scale_type_stretch), - 3 to stringResource(R.string.scale_type_fit_width), - 4 to stringResource(R.string.scale_type_fit_height), - 5 to stringResource(R.string.scale_type_original_size), - 6 to stringResource(R.string.scale_type_smart_fit), - ), + entries = ReaderPreferences.ImageScaleType + .mapIndexed { index, it -> index + 1 to stringResource(it) } + .toMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.zoomStart(), title = stringResource(R.string.pref_zoom_start), - entries = mapOf( - 1 to stringResource(R.string.zoom_start_automatic), - 2 to stringResource(R.string.zoom_start_left), - 3 to stringResource(R.string.zoom_start_right), - 4 to stringResource(R.string.zoom_start_center), - ), - + entries = ReaderPreferences.ZoomStart + .mapIndexed { index, it -> index + 1 to stringResource(it) } + .toMap(), ), Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.cropBorders(), @@ -265,9 +255,9 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = navModePref, title = stringResource(R.string.pref_viewer_nav), - entries = stringArrayResource(id = R.array.webtoon_nav).let { - it.indices.zip(it).toMap() - }, + entries = ReaderPreferences.TapZones + .mapIndexed { index, it -> index to stringResource(it) } + .toMap(), ), Preference.PreferenceItem.ListPreference( pref = readerPreferences.webtoonNavInverted(), diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt index 06bbb37b1..78f20cb8f 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings import android.os.Build import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource @@ -15,8 +16,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import tachiyomi.core.preference.getAndSet import tachiyomi.presentation.core.components.CheckboxItem -import tachiyomi.presentation.core.components.SelectItem +import tachiyomi.presentation.core.components.SettingsFlowRow import tachiyomi.presentation.core.components.SliderItem +import tachiyomi.presentation.core.components.material.ChoiceChip @Composable internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) { @@ -122,12 +124,14 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) ) val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState() - SelectItem( - label = stringResource(R.string.pref_color_filter_mode), - options = colorFilterModes.toTypedArray(), - selectedIndex = colorFilterMode, - ) { - screenModel.preferences.colorFilterMode().set(it) + SettingsFlowRow(R.string.pref_color_filter_mode) { + colorFilterModes.mapIndexed { index, it -> + ChoiceChip( + isSelected = colorFilterMode == index, + onClick = { screenModel.preferences.colorFilterMode().set(index) }, + content = { Text(it) }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt index 02738eeda..990a1423d 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt @@ -1,6 +1,7 @@ package eu.kanade.presentation.reader.settings import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource @@ -9,25 +10,27 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import tachiyomi.presentation.core.components.CheckboxItem -import tachiyomi.presentation.core.components.HeadingItem -import tachiyomi.presentation.core.components.RadioItem +import tachiyomi.presentation.core.components.SettingsFlowRow +import tachiyomi.presentation.core.components.material.ChoiceChip + +private val themes = listOf( + R.string.black_background to 1, + R.string.gray_background to 2, + R.string.white_background to 0, + R.string.automatic_background to 3, +) @Composable internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { - // TODO: show this in a nicer way - HeadingItem(R.string.pref_reader_theme) val readerTheme by screenModel.preferences.readerTheme().collectAsState() - listOf( - R.string.black_background to 1, - R.string.gray_background to 2, - R.string.white_background to 0, - R.string.automatic_background to 3, - ).map { (titleRes, theme) -> - RadioItem( - label = stringResource(titleRes), - selected = readerTheme == theme, - onClick = { screenModel.preferences.readerTheme().set(theme) }, - ) + SettingsFlowRow(R.string.pref_reader_theme) { + themes.map { (labelRes, value) -> + ChoiceChip( + isSelected = readerTheme == value, + onClick = { screenModel.preferences.readerTheme().set(value) }, + content = { Text(stringResource(labelRes)) }, + ) + } } val showPageNumber by screenModel.preferences.showPageNumber().collectAsState() diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt index 44f1bc27f..cf5b08997 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/ReaderSettingsDialog.kt @@ -23,9 +23,6 @@ fun ReaderSettingsDialog( onHideMenus: () -> Unit, screenModel: ReaderSettingsScreenModel, ) { - // TODO: undimming doesn't seem to work - val window = (LocalView.current.parent as? DialogWindowProvider)?.window - val tabTitles = listOf( stringResource(R.string.pref_category_reading_mode), stringResource(R.string.pref_category_general), @@ -33,16 +30,6 @@ fun ReaderSettingsDialog( ) val pagerState = rememberPagerState { tabTitles.size } - LaunchedEffect(pagerState.currentPage) { - if (pagerState.currentPage == 2) { - window?.setDimAmount(0f) - onHideMenus() - } else { - window?.setDimAmount(0.75f) - onShowMenus() - } - } - TabbedDialog( onDismissRequest = { onDismissRequest() @@ -51,6 +38,18 @@ fun ReaderSettingsDialog( tabTitles = tabTitles, pagerState = pagerState, ) { page -> + val window = (LocalView.current.parent as? DialogWindowProvider)?.window + + LaunchedEffect(pagerState.currentPage) { + if (pagerState.currentPage == 2) { + window?.setDimAmount(0f) + onHideMenus() + } else { + window?.setDimAmount(0.5f) + onShowMenus() + } + } + Column( modifier = Modifier .padding(vertical = TabbedDialogPaddings.Vertical) diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt index ff710a76d..7ada769df 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt @@ -2,40 +2,100 @@ package eu.kanade.presentation.reader.settings import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource +import eu.kanade.domain.manga.model.orientationType +import eu.kanade.domain.manga.model.readingModeType import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel +import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.util.system.isReleaseBuildType import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem +import tachiyomi.presentation.core.components.SelectItem import tachiyomi.presentation.core.components.SliderItem import java.text.NumberFormat +private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it } +private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it } +private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it } + @Composable internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) { HeadingItem(R.string.pref_category_for_this_series) + val manga by screenModel.mangaFlow.collectAsState() - // Reading mode - // Rotation type + val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) } + SelectItem( + label = stringResource(R.string.pref_category_reading_mode), + options = readingModeOptions.map { stringResource(it.first) }.toTypedArray(), + selectedIndex = readingModeOptions.indexOfFirst { it.second == readingMode }, + ) { + screenModel.onChangeReadingMode(readingModeOptions[it].second) + } - // if (pager) - PagerViewerSettings(screenModel) + val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) } + SelectItem( + label = stringResource(R.string.rotation_type), + options = orientationTypeOptions.map { stringResource(it.first) }.toTypedArray(), + selectedIndex = orientationTypeOptions.indexOfFirst { it.second == orientationType }, + ) { + screenModel.onChangeOrientation(orientationTypeOptions[it].second) + } - WebtoonViewerSettings(screenModel) + val viewer by screenModel.viewerFlow.collectAsState() + if (viewer is WebtoonViewer) { + WebtoonViewerSettings(screenModel) + } else { + PagerViewerSettings(screenModel) + } } @Composable private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) { HeadingItem(R.string.pager_viewer) - // Tap zones - // Invert tap zones - // Scale type - // Zoom start position + val navigationModePager by screenModel.preferences.navigationModePager().collectAsState() + SelectItem( + label = stringResource(R.string.pref_viewer_nav), + options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(), + selectedIndex = navigationModePager, + onSelect = { screenModel.preferences.navigationModePager().set(it) }, + ) + + if (navigationModePager != 5) { + val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState() + SelectItem( + label = stringResource(R.string.pref_read_with_tapping_inverted), + options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(), + selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == pagerNavInverted }, + onSelect = { + screenModel.preferences.pagerNavInverted().set(tappingInvertModeOptions[it].second) + }, + ) + } + + val imageScaleType by screenModel.preferences.imageScaleType().collectAsState() + SelectItem( + label = stringResource(R.string.pref_image_scale_type), + options = ReaderPreferences.ImageScaleType.map { stringResource(it) }.toTypedArray(), + selectedIndex = imageScaleType - 1, + onSelect = { screenModel.preferences.imageScaleType().set(it + 1) }, + ) + + val zoomStart by screenModel.preferences.zoomStart().collectAsState() + SelectItem( + label = stringResource(R.string.pref_zoom_start), + options = ReaderPreferences.ZoomStart.map { stringResource(it) }.toTypedArray(), + selectedIndex = zoomStart - 1, + onSelect = { screenModel.preferences.zoomStart().set(it + 1) }, + ) val cropBorders by screenModel.preferences.cropBorders().collectAsState() CheckboxItem( @@ -111,8 +171,25 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM HeadingItem(R.string.webtoon_viewer) - // TODO: Tap zones - // TODO: Invert tap zones + val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState() + SelectItem( + label = stringResource(R.string.pref_viewer_nav), + options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(), + selectedIndex = navigationModeWebtoon, + onSelect = { screenModel.preferences.navigationModeWebtoon().set(it) }, + ) + + if (navigationModeWebtoon != 5) { + val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState() + SelectItem( + label = stringResource(R.string.pref_read_with_tapping_inverted), + options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(), + selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == webtoonNavInverted }, + onSelect = { + screenModel.preferences.webtoonNavInverted().set(tappingInvertModeOptions[it].second) + }, + ) + } val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState() SliderItem( diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 02246a30d..e8dbe23ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -41,7 +41,12 @@ internal object ExtensionLoader { const val LIB_VERSION_MIN = 1.3 const val LIB_VERSION_MAX = 1.5 - private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES + private val PACKAGE_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNING_CERTIFICATES + } else { + @Suppress("DEPRECATION") + PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES + } // inorichi's key private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" @@ -49,7 +54,7 @@ internal object ExtensionLoader { /** * List of the trusted signatures. */ - var trustedSignatures = mutableSetOf() + preferences.trustedSignatures().get() + officialSignature + var trustedSignatures = mutableSetOf(officialSignature) + preferences.trustedSignatures().get() /** * Return a list of all the installed extensions initialized concurrently. @@ -59,7 +64,6 @@ internal object ExtensionLoader { fun loadExtensions(context: Context): List { val pkgManager = context.packageManager - @Suppress("DEPRECATION") val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PACKAGE_FLAGS.toLong())) } else { @@ -135,7 +139,7 @@ internal object ExtensionLoader { return LoadResult.Error } - val signatureHash = getSignatureHash(pkgInfo) + val signatureHash = getSignatureHash(context, pkgInfo) if (signatureHash == null) { logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } return LoadResult.Error @@ -220,12 +224,8 @@ internal object ExtensionLoader { * * @param pkgInfo The package info of the application. */ - private fun getSignatureHash(pkgInfo: PackageInfo): String? { - val signatures = pkgInfo.signatures - return if (signatures != null && signatures.isNotEmpty()) { - Hash.sha256(signatures.first().toByteArray()) - } else { - null - } + private fun getSignatureHash(context: Context, pkgInfo: PackageInfo): String? { + val signatures = PackageInfoCompat.getSignatures(context.packageManager, pkgInfo.packageName) + return signatures.firstOrNull()?.let { Hash.sha256(it.toByteArray()) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt index 496a40c58..137af5eff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt @@ -16,6 +16,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf @@ -313,6 +314,7 @@ internal class MigrateDialogScreenModel( ) } + @Immutable data class State( val isMigrating: Boolean = false, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index 73ad96780..ae89d9cb0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.MigrateSearchScreen import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.manga.MangaScreen +// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic class MigrateSearchScreen(private val mangaId: Long) : Screen() { @Composable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt index 82ed21be7..78c449fd0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt @@ -35,6 +35,7 @@ class GlobalSearchScreen( var showSingleLoadingScreen by remember { mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1) } + val filteredSources by screenModel.searchPagerFlow.collectAsState() if (showSingleLoadingScreen) { LoadingScreen() @@ -57,10 +58,13 @@ class GlobalSearchScreen( } else { GlobalSearchScreen( state = state, + items = filteredSources, navigateUp = navigator::pop, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = screenModel::search, getManga = { screenModel.getManga(it) }, + onChangeSearchFilter = screenModel::setSourceFilter, + onToggleResults = screenModel::toggleFilterResults, onClickSource = { if (!screenModel.incognitoMode.get()) { screenModel.lastUsedSourceId.set(it.id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt index 5c3d2b00b..13ac00468 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt @@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch import androidx.compose.runtime.Immutable import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.source.CatalogueSource +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt @@ -15,11 +20,18 @@ class GlobalSearchScreenModel( preferences: BasePreferences = Injekt.get(), private val sourcePreferences: SourcePreferences = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), -) : SearchScreenModel(GlobalSearchState(searchQuery = initialQuery)) { +) : SearchScreenModel(State(searchQuery = initialQuery)) { val incognitoMode = preferences.incognitoMode() val lastUsedSourceId = sourcePreferences.lastUsedSource() + val searchPagerFlow = state.map { Pair(it.onlyShowHasResults, it.items) } + .distinctUntilChanged() + .map { (onlyShowHasResults, items) -> + items.filter { (_, result) -> result.isVisible(onlyShowHasResults) } + } + .stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items) + init { extensionFilter = initialExtensionFilter if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) { @@ -33,6 +45,7 @@ class GlobalSearchScreenModel( val pinnedSources = sourcePreferences.pinnedSources().get() return sourceManager.getCatalogueSources() + .filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources } .filter { it.lang in enabledLanguages } .filterNot { "${it.id}" in disabledSources } .sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" })) @@ -53,15 +66,29 @@ class GlobalSearchScreenModel( override fun getItems(): Map { return mutableState.value.items } -} - -@Immutable -data class GlobalSearchState( - val searchQuery: String? = null, - val items: Map = emptyMap(), -) { - - val progress: Int = items.count { it.value !is SearchItemResult.Loading } - - val total: Int = items.size + + fun setSourceFilter(filter: SourceFilter) { + mutableState.update { it.copy(sourceFilter = filter) } + } + + fun toggleFilterResults() { + mutableState.update { + it.copy(onlyShowHasResults = !it.onlyShowHasResults) + } + } + + private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean { + return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty) + } + + @Immutable + data class State( + val searchQuery: String? = null, + val sourceFilter: SourceFilter = SourceFilter.PinnedOnly, + val onlyShowHasResults: Boolean = false, + val items: Map = emptyMap(), + ) { + val progress: Int = items.count { it.value !is SearchItemResult.Loading } + val total: Int = items.size + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 78238b21b..26ab767c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -68,16 +68,7 @@ abstract class SearchScreenModel( val enabledSources = getEnabledSources() if (filter.isEmpty()) { - val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get() - val pinnedSources = sourcePreferences.pinnedSources().get() - - return enabledSources.filter { - if (shouldSearchPinnedOnly) { - "${it.id}" in pinnedSources - } else { - true - } - } + return enabledSources } return extensionManager.installedExtensionsFlow.value @@ -137,6 +128,11 @@ abstract class SearchScreenModel( } } +enum class SourceFilter { + All, + PinnedOnly, +} + sealed class SearchItemResult { object Loading : SearchItemResult() 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 4612eec34..9fd4674fe 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 @@ -113,6 +113,10 @@ class MainActivity : BaseActivity() { private var navigator: Navigator? = null + init { + registerSecureActivity(this) + } + override fun onCreate(savedInstanceState: Bundle?) { val isLaunch = savedInstanceState == null @@ -429,10 +433,6 @@ class MainActivity : BaseActivity() { return true } - init { - registerSecureActivity(this) - } - companion object { // Splash screen private const val SPLASH_MIN_DURATION = 500 // ms diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index 1ed1895a9..04c5cd40d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -265,6 +266,7 @@ data class TrackInfoDialogHomeScreen( .filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true } } + @Immutable data class State( val trackItems: List = emptyList(), ) @@ -314,6 +316,7 @@ private data class TrackStatusSelectorScreen( } } + @Immutable data class State( val selection: Int, ) @@ -369,6 +372,7 @@ private data class TrackChapterSelectorScreen( } } + @Immutable data class State( val selection: Int, ) @@ -419,6 +423,7 @@ private data class TrackScoreSelectorScreen( } } + @Immutable data class State( val selection: String, ) @@ -724,6 +729,7 @@ data class TrackServiceSearchScreen( mutableState.update { it.copy(selected = selected) } } + @Immutable data class State( val queryResult: Result>? = null, val selected: TrackSearch? = null, 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 a8acff615..f7afede9e 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 @@ -69,7 +69,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer @@ -391,7 +390,13 @@ class ReaderActivity : BaseActivity() { binding.dialogRoot.setComposeContent { val state by viewModel.state.collectAsState() - val settingsScreenModel = remember { ReaderSettingsScreenModel() } + val settingsScreenModel = remember { + ReaderSettingsScreenModel( + readerState = viewModel.state, + onChangeReadingMode = viewModel::setMangaReadingMode, + onChangeOrientation = viewModel::setMangaOrientationType, + ) + } val onDismissRequest = viewModel::closeDialog when (state.dialog) { @@ -485,7 +490,7 @@ class ReaderActivity : BaseActivity() { ) { val newReadingMode = ReadingModeType.fromPreference(itemId) - viewModel.setMangaReadingMode(newReadingMode.flagValue) + viewModel.setMangaReadingMode(newReadingMode) menuToggleToast?.cancel() if (!readerPreferences.showReadingMode().get()) { @@ -539,7 +544,7 @@ class ReaderActivity : BaseActivity() { ) { val newOrientation = OrientationType.fromPreference(itemId) - viewModel.setMangaOrientationType(newOrientation.flagValue) + viewModel.setMangaOrientationType(newOrientation) menuToggleToast?.cancel() menuToggleToast = toast(newOrientation.stringRes) @@ -548,16 +553,6 @@ class ReaderActivity : BaseActivity() { } // Settings sheet - with(binding.actionSettingsLegacy) { - setTooltip(R.string.action_settings) - - var readerSettingSheet: ReaderSettingsSheet? = null - - setOnClickListener { - if (readerSettingSheet?.isShowing == true) return@setOnClickListener - readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() } - } - } with(binding.actionSettings) { setTooltip(R.string.action_settings) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 7a7a313a6..797096738 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader import android.app.Application import android.net.Uri +import androidx.compose.runtime.Immutable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -601,10 +602,10 @@ class ReaderViewModel( /** * Updates the viewer position for the open manga. */ - fun setMangaReadingMode(readingModeType: Int) { + fun setMangaReadingMode(readingModeType: ReadingModeType) { val manga = manga ?: return runBlocking(Dispatchers.IO) { - setMangaViewerFlags.awaitSetMangaReadingMode(manga.id, readingModeType.toLong()) + setMangaViewerFlags.awaitSetMangaReadingMode(manga.id, readingModeType.flagValue.toLong()) val currChapters = state.value.viewerChapters if (currChapters != null) { // Save current page @@ -637,10 +638,10 @@ class ReaderViewModel( /** * Updates the orientation type for the open manga. */ - fun setMangaOrientationType(rotationType: Int) { + fun setMangaOrientationType(rotationType: OrientationType) { val manga = manga ?: return viewModelScope.launchIO { - setMangaViewerFlags.awaitSetOrientationType(manga.id, rotationType.toLong()) + setMangaViewerFlags.awaitSetOrientationType(manga.id, rotationType.flagValue.toLong()) val currChapters = state.value.viewerChapters if (currChapters != null) { // Save current page @@ -790,16 +791,12 @@ class ReaderViewModel( } } - /** - * Results of the set as cover feature. - */ enum class SetAsCoverResult { - Success, AddToLibraryFirst, Error + Success, + AddToLibraryFirst, + Error, } - /** - * Results of the save image feature. - */ sealed class SaveImageResult { class Success(val uri: Uri) : SaveImageResult() class Error(val error: Throwable) : SaveImageResult() @@ -844,6 +841,7 @@ class ReaderViewModel( } } + @Immutable data class State( val manga: Manga? = null, val viewerChapters: ViewerChapters? = null, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt index fb7df6282..0125a78a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt @@ -18,8 +18,6 @@ enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val str companion object { const val MASK = 0x00000038 - fun fromPreference(preference: Int?): OrientationType = values().find { it.flagValue == preference } ?: FREE - - fun fromSpinner(position: Int?) = values().find { value -> value.prefValue == position } ?: DEFAULT + fun fromPreference(preference: Int?): OrientationType = values().find { it.flagValue == preference } ?: DEFAULT } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index b18a5682e..23a5beb13 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.setting +import androidx.annotation.StringRes +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.isReleaseBuildType import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.getEnum @@ -122,11 +124,15 @@ class ReaderPreferences( // endregion - enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) { - NONE, - HORIZONTAL(shouldInvertHorizontal = true), - VERTICAL(shouldInvertVertical = true), - BOTH(shouldInvertHorizontal = true, shouldInvertVertical = true), + enum class TappingInvertMode( + @StringRes val titleResId: Int, + val shouldInvertHorizontal: Boolean = false, + val shouldInvertVertical: Boolean = false, + ) { + NONE(R.string.tapping_inverted_none), + HORIZONTAL(R.string.tapping_inverted_horizontal, shouldInvertHorizontal = true), + VERTICAL(R.string.tapping_inverted_vertical, shouldInvertVertical = true), + BOTH(R.string.tapping_inverted_both, shouldInvertHorizontal = true, shouldInvertVertical = true), } enum class ReaderHideThreshold(val threshold: Int) { @@ -139,5 +145,30 @@ class ReaderPreferences( companion object { const val WEBTOON_PADDING_MIN = 0 const val WEBTOON_PADDING_MAX = 25 + + val TapZones = listOf( + R.string.label_default, + R.string.l_nav, + R.string.kindlish_nav, + R.string.edge_nav, + R.string.right_and_left_nav, + R.string.disabled_nav, + ) + + val ImageScaleType = listOf( + R.string.scale_type_fit_screen, + R.string.scale_type_stretch, + R.string.scale_type_fit_width, + R.string.scale_type_fit_height, + R.string.scale_type_original_size, + R.string.scale_type_smart_fit, + ) + + val ZoomStart = listOf( + R.string.zoom_start_automatic, + R.string.zoom_start_left, + R.string.zoom_start_right, + R.string.zoom_start_center, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt index 6a9ad2936..11125ac77 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt @@ -1,15 +1,35 @@ package eu.kanade.tachiyomi.ui.reader.setting import cafe.adriel.voyager.core.model.ScreenModel +import eu.kanade.presentation.util.ioCoroutineScope +import eu.kanade.tachiyomi.ui.reader.ReaderViewModel import eu.kanade.tachiyomi.util.preference.toggle +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import tachiyomi.core.preference.Preference import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class ReaderSettingsScreenModel( + readerState: StateFlow, + val onChangeReadingMode: (ReadingModeType) -> Unit, + val onChangeOrientation: (OrientationType) -> Unit, val preferences: ReaderPreferences = Injekt.get(), ) : ScreenModel { + val viewerFlow = readerState + .map { it.viewer } + .distinctUntilChanged() + .stateIn(ioCoroutineScope, SharingStarted.Lazily, null) + + val mangaFlow = readerState + .map { it.manga } + .distinctUntilChanged() + .stateIn(ioCoroutineScope, SharingStarted.Lazily, null) + fun togglePreference(preference: (ReaderPreferences) -> Preference) { preference(preferences).toggle() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt deleted file mode 100644 index 50529e4cb..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt +++ /dev/null @@ -1,90 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader.setting - -import android.os.Bundle -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.google.android.material.bottomsheet.BottomSheetDialog -import eu.kanade.domain.manga.model.orientationType -import eu.kanade.domain.manga.model.readingModeType -import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding -import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer -import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer -import eu.kanade.tachiyomi.util.preference.bindToPreference -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import uy.kohesive.injekt.injectLazy - -class ReaderSettingsSheet( - private val activity: ReaderActivity, -) : BottomSheetDialog(activity) { - - private val readerPreferences: ReaderPreferences by injectLazy() - - private lateinit var binding: ReaderReadingModeSettingsBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = ReaderReadingModeSettingsBinding.inflate(activity.layoutInflater) - setContentView(binding.root) - - initGeneralPreferences() - - when (activity.viewModel.state.value.viewer) { - is PagerViewer -> initPagerPreferences() - is WebtoonViewer -> initWebtoonPreferences() - } - } - - private fun initGeneralPreferences() { - binding.viewer.onItemSelectedListener = { position -> - val readingModeType = ReadingModeType.fromSpinner(position) - activity.viewModel.setMangaReadingMode(readingModeType.flagValue) - - val mangaViewer = activity.viewModel.getMangaReadingMode() - if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) { - initWebtoonPreferences() - } else { - initPagerPreferences() - } - } - binding.viewer.setSelection(activity.viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue) - - binding.rotationMode.onItemSelectedListener = { position -> - val rotationType = OrientationType.fromSpinner(position) - activity.viewModel.setMangaOrientationType(rotationType.flagValue) - } - binding.rotationMode.setSelection(activity.viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue) - } - - private fun initPagerPreferences() { - binding.webtoonPrefsGroup.root.isVisible = false - binding.pagerPrefsGroup.root.isVisible = true - - binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), ReaderPreferences.TappingInvertMode::class.java) - - binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager()) - readerPreferences.navigationModePager().changes() - .onEach { - val isTappingEnabled = it != 5 - binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled - } - .launchIn(activity.lifecycleScope) - binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1) - - binding.pagerPrefsGroup.zoomStart.bindToPreference(readerPreferences.zoomStart(), 1) - } - - private fun initWebtoonPreferences() { - binding.pagerPrefsGroup.root.isVisible = false - binding.webtoonPrefsGroup.root.isVisible = true - - binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), ReaderPreferences.TappingInvertMode::class.java) - - binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon()) - readerPreferences.navigationModeWebtoon().changes() - .onEach { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 } - .launchIn(activity.lifecycleScope) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt index 88e1a3f02..73975e28f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt @@ -29,8 +29,6 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL } - fun fromSpinner(position: Int?) = values().find { value -> value.prefValue == position } ?: DEFAULT - fun toViewer(preference: Int?, activity: ReaderActivity): Viewer { return when (fromPreference(preference)) { LEFT_TO_RIGHT -> L2RPagerViewer(activity) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt index 7009635c6..887fc7e2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt @@ -1,16 +1,7 @@ package eu.kanade.tachiyomi.util.preference -import android.widget.CompoundButton import tachiyomi.core.preference.Preference -/** - * Binds a checkbox or switch view with a boolean preference. - */ -fun CompoundButton.bindToPreference(pref: Preference) { - isChecked = pref.get() - setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) } -} - operator fun Preference>.plusAssign(item: T) { set(get() + item) } 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 9124619f8..8cc74445b 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 @@ -134,7 +134,6 @@ private fun Context.defaultBrowserPackageName(): String? { val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())) } else { - @Suppress("DEPRECATION") packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY) } return resolveInfo diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt index e93905c74..50f45ae85 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -5,10 +5,7 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.os.Build -import android.view.Display import android.view.View -import android.view.WindowManager -import androidx.core.content.getSystemService import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.model.TabletUiMode import uy.kohesive.injekt.Injekt @@ -67,14 +64,6 @@ fun Context.isNightMode(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES } -val Context.displayCompat: Display? - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - display - } else { - @Suppress("DEPRECATION") - getSystemService()?.defaultDisplay - } - val Resources.isLTR get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/BottomSheetBehaviorExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/BottomSheetBehaviorExtensions.kt deleted file mode 100644 index a5da1a432..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/BottomSheetBehaviorExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -@file:Suppress("PackageDirectoryMismatch") - -package com.google.android.material.bottomsheet - -import android.view.View - -/** - * Returns package-private elevation value - */ -fun BottomSheetBehavior.getElevation(): Float { - return elevation.takeIf { it >= 0F } ?: 0F -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt index fc5a2fc1f..1a60b8e1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt @@ -1,30 +1,7 @@ package eu.kanade.tachiyomi.util.view -import android.content.Context -import android.graphics.Color import android.view.Window import android.view.WindowManager -import com.google.android.material.elevation.ElevationOverlayProvider -import eu.kanade.tachiyomi.util.system.getResourceColor -import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim - -/** - * Sets navigation bar color to transparent if system's config_navBarNeedsScrim is false, - * otherwise it will use the theme navigationBarColor with 70% opacity. - * - * @see isNavigationBarNeedsScrim - */ -fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float = 0F) { - navigationBarColor = if (context.isNavigationBarNeedsScrim()) { - // Set navbar scrim 70% of navigationBarColor - ElevationOverlayProvider(context).compositeOverlayIfNeeded( - context.getResourceColor(android.R.attr.navigationBarColor, 0.7F), - elevation, - ) - } else { - Color.TRANSPARENT - } -} fun Window.setSecureScreen(enabled: Boolean) { if (enabled) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialSpinnerView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialSpinnerView.kt deleted file mode 100644 index 57289daf1..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/MaterialSpinnerView.kt +++ /dev/null @@ -1,154 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.annotation.SuppressLint -import android.content.Context -import android.util.AttributeSet -import android.view.Gravity -import android.view.LayoutInflater -import android.view.MenuItem -import android.widget.FrameLayout -import androidx.appcompat.content.res.AppCompatResources -import androidx.appcompat.view.menu.MenuBuilder -import androidx.appcompat.widget.PopupMenu -import androidx.core.content.withStyledAttributes -import androidx.core.view.forEach -import androidx.core.view.get -import androidx.core.view.size -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.databinding.PrefSpinnerBinding -import eu.kanade.tachiyomi.util.system.getResourceColor -import tachiyomi.core.preference.Preference - -class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - FrameLayout(context, attrs) { - - private var entries = emptyList() - private var selectedPosition = 0 - private var popup: PopupMenu? = null - - var onItemSelectedListener: ((Int) -> Unit)? = null - set(value) { - field = value - if (value != null) { - popup = makeSettingsPopup() - setOnTouchListener(popup?.dragToOpenListener) - setOnClickListener { - popup?.show() - } - } - } - - private val emptyIcon by lazy { - AppCompatResources.getDrawable(context, R.drawable.ic_blank_24dp) - } - private val checkmarkIcon by lazy { - AppCompatResources.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply { - setTint(context.getResourceColor(android.R.attr.textColorPrimary)) - } - } - - private val binding = PrefSpinnerBinding.inflate(LayoutInflater.from(context), this, false) - - init { - addView(binding.root) - - context.withStyledAttributes(set = attrs, attrs = R.styleable.MaterialSpinnerView) { - val title = getString(R.styleable.MaterialSpinnerView_title).orEmpty() - binding.title.text = title - - val viewEntries = getTextArray(R.styleable.MaterialSpinnerView_android_entries) - .orEmpty() - .map { it.toString() } - entries = viewEntries - binding.details.text = viewEntries.firstOrNull().orEmpty() - } - } - - fun setSelection(selection: Int) { - if (selectedPosition < (popup?.menu?.size ?: 0)) { - popup?.menu?.getItem(selectedPosition)?.let { - it.icon = emptyIcon - } - } - selectedPosition = selection - popup?.menu?.getItem(selectedPosition)?.let { - it.icon = checkmarkIcon - } - binding.details.text = entries.getOrNull(selection).orEmpty() - } - - fun bindToPreference(pref: Preference, offset: Int = 0, block: ((Int) -> Unit)? = null) { - setSelection(pref.get() - offset) - - popup = makeSettingsPopup(pref, offset, block) - setOnTouchListener(popup?.dragToOpenListener) - setOnClickListener { - popup?.show() - } - } - - fun > bindToPreference(pref: Preference, clazz: Class) { - val enumConstants = clazz.enumConstants - enumConstants?.indexOf(pref.get())?.let { setSelection(it) } - - popup = makeSettingsPopup(pref, clazz) - setOnTouchListener(popup?.dragToOpenListener) - setOnClickListener { - popup?.show() - } - } - - private fun > makeSettingsPopup(preference: Preference, clazz: Class): PopupMenu { - return createPopupMenu { pos -> - onItemSelectedListener?.invoke(pos) - - val enumConstants = clazz.enumConstants - enumConstants?.get(pos)?.let { enumValue -> preference.set(enumValue) } - } - } - - private fun makeSettingsPopup(preference: Preference, intValues: List, block: ((Int) -> Unit)? = null): PopupMenu { - return createPopupMenu { pos -> - preference.set(intValues[pos] ?: 0) - block?.invoke(pos) - } - } - - private fun makeSettingsPopup(preference: Preference, offset: Int = 0, block: ((Int) -> Unit)? = null): PopupMenu { - return createPopupMenu { pos -> - preference.set(pos + offset) - block?.invoke(pos) - } - } - - private fun makeSettingsPopup(): PopupMenu { - return createPopupMenu { pos -> - onItemSelectedListener?.invoke(pos) - } - } - - private fun menuClicked(menuItem: MenuItem): Int { - val pos = menuItem.itemId - setSelection(pos) - return pos - } - - @SuppressLint("RestrictedApi") - fun createPopupMenu(onItemClick: (Int) -> Unit): PopupMenu { - val popup = PopupMenu(context, this, Gravity.END, R.attr.actionOverflowMenuStyle, 0) - entries.forEachIndexed { index, entry -> - popup.menu.add(0, index, 0, entry) - } - (popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true) - popup.menu.forEach { - it.icon = emptyIcon - } - popup.menu[selectedPosition].icon = checkmarkIcon - popup.setOnMenuItemClickListener { menuItem -> - val pos = menuClicked(menuItem) - onItemClick(pos) - true - } - return popup - } -} diff --git a/app/src/main/res/anim/bottom_sheet_slide_in.xml b/app/src/main/res/anim/bottom_sheet_slide_in.xml deleted file mode 100644 index 74f052a3f..000000000 --- a/app/src/main/res/anim/bottom_sheet_slide_in.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/app/src/main/res/anim/bottom_sheet_slide_out.xml b/app/src/main/res/anim/bottom_sheet_slide_out.xml deleted file mode 100644 index 553c827a5..000000000 --- a/app/src/main/res/anim/bottom_sheet_slide_out.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_expand_more_24dp.xml b/app/src/main/res/drawable/ic_expand_more_24dp.xml deleted file mode 100644 index 5f5ba4e44..000000000 --- a/app/src/main/res/drawable/ic_expand_more_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/pref_spinner.xml b/app/src/main/res/layout/pref_spinner.xml deleted file mode 100644 index c7335fa1f..000000000 --- a/app/src/main/res/layout/pref_spinner.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index afd40f4c9..b5dd2778b 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -118,19 +118,6 @@ app:srcCompat="@drawable/ic_screen_rotation_24dp" app:tint="?attr/colorOnSurface" /> - - diff --git a/app/src/main/res/layout/reader_pager_settings.xml b/app/src/main/res/layout/reader_pager_settings.xml deleted file mode 100644 index f79bcfa11..000000000 --- a/app/src/main/res/layout/reader_pager_settings.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/reader_reading_mode_settings.xml b/app/src/main/res/layout/reader_reading_mode_settings.xml deleted file mode 100644 index e825c229a..000000000 --- a/app/src/main/res/layout/reader_reading_mode_settings.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/reader_webtoon_settings.xml b/app/src/main/res/layout/reader_webtoon_settings.xml deleted file mode 100644 index 00e970665..000000000 --- a/app/src/main/res/layout/reader_webtoon_settings.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index d9403c687..11d827db9 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -9,22 +9,6 @@ @string/vertical_plus_viewer - - @string/scale_type_fit_screen - @string/scale_type_stretch - @string/scale_type_fit_width - @string/scale_type_fit_height - @string/scale_type_original_size - @string/scale_type_smart_fit - - - - @string/zoom_start_automatic - @string/zoom_start_left - @string/zoom_start_right - @string/zoom_start_center - - @string/label_default @string/rotation_free @@ -34,29 +18,4 @@ @string/rotation_force_landscape @string/rotation_reverse_portrait - - - @string/tapping_inverted_none - @string/tapping_inverted_horizontal - @string/tapping_inverted_vertical - @string/tapping_inverted_both - - - - @string/label_default - @string/l_nav - @string/kindlish_nav - @string/edge_nav - @string/right_and_left_nav - @string/disabled_nav - - - - @string/label_default - @string/l_nav - @string/kindlish_nav - @string/edge_nav - @string/right_and_left_nav - @string/disabled_nav - diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml deleted file mode 100644 index d644cdf85..000000000 --- a/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f6f722017..e82288767 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,6 +1,4 @@ - 8dp - 16dp 16dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fdcafe4c4..14dc1a13a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -20,33 +20,6 @@ - - - - - - - - - - - - - @@ -71,18 +44,6 @@ - - - - - - @@ -94,16 +55,6 @@ @bool/elevationOverlayEnabled - - - - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 50aa51d0e..b61c34eb9 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -64,17 +64,13 @@ 0.32 true @style/Theme.Tachiyomi.ActionButton.Overflow - @drawable/ic_close_24dp @style/PreferenceThemeOverlay.Tachiyomi - @style/ThemeOverlay.Tachiyomi.BottomSheetDialog @style/Widget.Material3.TextInputLayout.OutlinedBox @style/Widget.Material3.AppBarLayout @style/Widget.Material3.Toolbar.Surface - @style/Widget.Tachiyomi.TabLayout @style/Widget.Tachiyomi.Switch @style/Widget.Material3.CompoundButton.MaterialSwitch @style/Widget.Tachiyomi.Switch - @style/Widget.Tachiyomi.Slider @style/Widget.Material3.CardView.Elevated diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/core/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt index 3517d1dc9..4ecf3e09d 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt @@ -7,6 +7,7 @@ import tachiyomi.core.util.lang.withIOContext /** * Util for evaluating JavaScript in sources. */ +@Suppress("UNUSED", "UNCHECKED_CAST") class JavaScriptEngine(context: Context) { /** @@ -17,7 +18,6 @@ class JavaScriptEngine(context: Context) { * @param script JavaScript to execute. * @return Result of JavaScript code as a primitive type. */ - @Suppress("UNUSED", "UNCHECKED_CAST") suspend fun evaluate(script: String): T = withIOContext { QuickJs.create().use { it.evaluate(script) as T diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index efbdacb9d..38ba749d2 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -11,65 +11,60 @@ import java.io.File import java.util.concurrent.TimeUnit class NetworkHelper( - context: Context, + private val context: Context, private val preferences: NetworkPreferences, ) { - private val cacheDir = File(context.cacheDir, "network_cache") - private val cacheSize = 5L * 1024 * 1024 // 5 MiB - val cookieJar = AndroidCookieJar() - private val userAgentInterceptor by lazy { - UserAgentInterceptor(::defaultUserAgentProvider) - } - private val cloudflareInterceptor by lazy { - CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider) - } + val client: OkHttpClient = run { + val builder = OkHttpClient.Builder() + .cookieJar(cookieJar) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .callTimeout(2, TimeUnit.MINUTES) + .cache( + Cache( + directory = File(context.cacheDir, "network_cache"), + maxSize = 5L * 1024 * 1024, // 5 MiB + ), + ) + .addInterceptor(UncaughtExceptionInterceptor()) + .addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider)) - private val baseClientBuilder: OkHttpClient.Builder - get() { - val builder = OkHttpClient.Builder() - .cookieJar(cookieJar) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .callTimeout(2, TimeUnit.MINUTES) - .addInterceptor(UncaughtExceptionInterceptor()) - .addInterceptor(userAgentInterceptor) - - if (preferences.verboseLogging().get()) { - val httpLoggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.HEADERS - } - builder.addNetworkInterceptor(httpLoggingInterceptor) + if (preferences.verboseLogging().get()) { + val httpLoggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.HEADERS } - - when (preferences.dohProvider().get()) { - PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() - PREF_DOH_GOOGLE -> builder.dohGoogle() - PREF_DOH_ADGUARD -> builder.dohAdGuard() - PREF_DOH_QUAD9 -> builder.dohQuad9() - PREF_DOH_ALIDNS -> builder.dohAliDNS() - PREF_DOH_DNSPOD -> builder.dohDNSPod() - PREF_DOH_360 -> builder.doh360() - PREF_DOH_QUAD101 -> builder.dohQuad101() - PREF_DOH_MULLVAD -> builder.dohMullvad() - PREF_DOH_CONTROLD -> builder.dohControlD() - PREF_DOH_NJALLA -> builder.dohNajalla() - PREF_DOH_SHECAN -> builder.dohShecan() - } - - return builder + builder.addNetworkInterceptor(httpLoggingInterceptor) } - val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() } + builder.addInterceptor(CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)) - @Suppress("UNUSED") - val cloudflareClient by lazy { - client.newBuilder() - .addInterceptor(cloudflareInterceptor) - .build() + when (preferences.dohProvider().get()) { + PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() + PREF_DOH_GOOGLE -> builder.dohGoogle() + PREF_DOH_ADGUARD -> builder.dohAdGuard() + PREF_DOH_QUAD9 -> builder.dohQuad9() + PREF_DOH_ALIDNS -> builder.dohAliDNS() + PREF_DOH_DNSPOD -> builder.dohDNSPod() + PREF_DOH_360 -> builder.doh360() + PREF_DOH_QUAD101 -> builder.dohQuad101() + PREF_DOH_MULLVAD -> builder.dohMullvad() + PREF_DOH_CONTROLD -> builder.dohControlD() + PREF_DOH_NJALLA -> builder.dohNajalla() + PREF_DOH_SHECAN -> builder.dohShecan() + } + + builder.build() } + /** + * @deprecated Since extension-lib 1.5 + */ + @Deprecated("The regular client handles Cloudflare by default") + @Suppress("UNUSED") + val cloudflareClient: OkHttpClient = client + fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim() } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 62f495dfe..9f4197d5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 7f03576d2..96202894f 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -388,7 +388,7 @@ White Gray Black - Automatic + Auto Default reading mode L shaped Kindle-ish @@ -485,7 +485,6 @@ Track - Only search pinned sources in global search Hide entries already in library @@ -660,6 +659,7 @@ Latest Popular Browse + Has results Local source guide You have no pinned sources Chapter not found diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt index d5ce4a5d2..d40a7747b 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt @@ -4,6 +4,8 @@ import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer @@ -61,7 +63,10 @@ fun HeadingItem( style = MaterialTheme.typography.header, modifier = Modifier .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), ) } @@ -203,7 +208,10 @@ fun SelectItem( modifier = Modifier .menuAnchor() .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), label = { Text(text = label) }, value = options[selectedIndex].toString(), onValueChange = {}, @@ -259,7 +267,10 @@ fun TriStateItem( }, ) .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(24.dp), ) { @@ -306,6 +317,26 @@ fun TextItem( ) } +@Composable +fun SettingsFlowRow( + @StringRes labelRes: Int, + content: @Composable FlowRowScope.() -> Unit, +) { + Column { + HeadingItem(labelRes) + FlowRow( + modifier = Modifier.padding( + start = SettingsItemsPaddings.Horizontal, + top = 0.dp, + end = SettingsItemsPaddings.Horizontal, + bottom = SettingsItemsPaddings.Vertical, + ), + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = content, + ) + } +} + @Composable private fun BaseSettingsItem( label: String, @@ -316,7 +347,10 @@ private fun BaseSettingsItem( modifier = Modifier .clickable(onClick = onClick) .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + .padding( + horizontal = SettingsItemsPaddings.Horizontal, + vertical = SettingsItemsPaddings.Vertical, + ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(24.dp), ) { diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Chip.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Chip.kt new file mode 100644 index 000000000..55d988f52 --- /dev/null +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Chip.kt @@ -0,0 +1,60 @@ +package tachiyomi.presentation.core.components.material + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun Chip( + modifier: Modifier = Modifier, + backgroundColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f), + contentColor: Color = MaterialTheme.colorScheme.onSurface, + onClick: () -> Unit = {}, + content: @Composable () -> Unit, +) { + Surface( + modifier = Modifier, + shape = CircleShape, + color = backgroundColor, + contentColor = contentColor, + onClick = {}, + ) { + Row( + modifier = modifier.clickable(onClick = onClick) + .widthIn(min = 56.dp) + .requiredHeight(32.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + ProvideTextStyle(MaterialTheme.typography.bodySmall, content) + } + } +} + +@Composable +fun ChoiceChip( + modifier: Modifier = Modifier, + isSelected: Boolean, + onClick: () -> Unit = {}, + selectedBackgroundColor: Color = MaterialTheme.colorScheme.primary, + selectedContentColor: Color = MaterialTheme.colorScheme.onPrimary, + content: @Composable () -> Unit, +) { + if (isSelected) { + Chip(modifier, selectedBackgroundColor, selectedContentColor, onClick, content) + } else { + Chip(modifier, onClick = onClick, content = content) + } +} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/SegmentedButtons.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/SegmentedButtons.kt new file mode 100644 index 000000000..7656af007 --- /dev/null +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/SegmentedButtons.kt @@ -0,0 +1,90 @@ +package tachiyomi.presentation.core.components.material + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview + +val StartItemShape = RoundedCornerShape(topStartPercent = 100, bottomStartPercent = 100) +val MiddleItemShape = RoundedCornerShape(0) +val EndItemShape = RoundedCornerShape(topEndPercent = 100, bottomEndPercent = 100) + +@Composable +fun SegmentedButtons( + modifier: Modifier = Modifier, + entries: List, + selectedIndex: Int, + onClick: (Int) -> Unit, +) { + Row( + modifier = modifier, + ) { + entries.mapIndexed { index, label -> + val shape = remember(entries, index) { + when (index) { + 0 -> StartItemShape + entries.lastIndex -> EndItemShape + else -> MiddleItemShape + } + } + + if (index == selectedIndex) { + Button( + modifier = Modifier.weight(1f), + shape = shape, + onClick = { onClick(index) }, + ) { + Text( + text = label, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } else { + OutlinedButton( + modifier = Modifier.weight(1f), + shape = shape, + onClick = { onClick(index) }, + ) { + Text( + text = label, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + } +} + +@Preview +@Composable +private fun SegmentedButtonsPreview() { + Column { + SegmentedButtons( + entries = listOf( + "Day", + "Week", + "Month", + "Year", + ), + selectedIndex = 1, + onClick = {}, + ) + + SegmentedButtons( + entries = listOf( + "Foo", + "Bar", + ), + selectedIndex = 1, + onClick = {}, + ) + } +} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt index b76407e60..d547ad8d0 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TabPosition -import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.SecondaryIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -50,8 +50,8 @@ fun TabIndicator( currentTabPosition: TabPosition, currentPageOffsetFraction: Float, ) { - TabRowDefaults.Indicator( - Modifier + SecondaryIndicator( + modifier = Modifier .tabIndicatorOffset(currentTabPosition, currentPageOffsetFraction) .padding(horizontal = 8.dp) .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),