mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
Merge branch 'master' into sync-part-1
This commit is contained in:
commit
abe69206dd
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<CatalogueSource, SearchItemResult>,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
@ -36,6 +56,7 @@ fun GlobalSearchScreen(
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||
GlobalSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
@ -45,10 +66,70 @@ fun GlobalSearchScreen(
|
||||
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,
|
||||
|
@ -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(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.DisplayPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
HeadingItem(R.string.action_display_mode)
|
||||
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
||||
listOf(
|
||||
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,
|
||||
).map { (titleRes, mode) ->
|
||||
RadioItem(
|
||||
label = stringResource(titleRes),
|
||||
selected = displayMode == mode,
|
||||
onClick = { screenModel.setDisplayMode(mode) },
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.DisplayPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
||||
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) {
|
||||
|
@ -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),
|
||||
|
@ -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(),
|
||||
|
@ -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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
@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(
|
||||
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,
|
||||
).map { (titleRes, theme) ->
|
||||
RadioItem(
|
||||
label = stringResource(titleRes),
|
||||
selected = readerTheme == theme,
|
||||
onClick = { screenModel.preferences.readerTheme().set(theme) },
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
||||
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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -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<String>() + 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<LoadResult> {
|
||||
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()) }
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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>(GlobalSearchState(searchQuery = initialQuery)) {
|
||||
) : SearchScreenModel<GlobalSearchScreenModel.State>(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<CatalogueSource, SearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
|
||||
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 GlobalSearchState(
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
||||
) {
|
||||
|
||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||
|
||||
val total: Int = items.size
|
||||
}
|
||||
}
|
||||
|
@ -68,16 +68,7 @@ abstract class SearchScreenModel<T>(
|
||||
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<T>(
|
||||
}
|
||||
}
|
||||
|
||||
enum class SourceFilter {
|
||||
All,
|
||||
PinnedOnly,
|
||||
}
|
||||
|
||||
sealed class SearchItemResult {
|
||||
object Loading : SearchItemResult()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<TrackItem> = 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<List<TrackSearch>>? = null,
|
||||
val selected: TrackSearch? = null,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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<ReaderViewModel.State>,
|
||||
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<Boolean>) {
|
||||
preference(preferences).toggle()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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<Boolean>) {
|
||||
isChecked = pref.get()
|
||||
setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) }
|
||||
}
|
||||
|
||||
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
|
||||
set(get() + item)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<WindowManager>()?.defaultDisplay
|
||||
}
|
||||
|
||||
val Resources.isLTR
|
||||
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
@file:Suppress("PackageDirectoryMismatch")
|
||||
|
||||
package com.google.android.material.bottomsheet
|
||||
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* Returns package-private elevation value
|
||||
*/
|
||||
fun <T : View> BottomSheetBehavior<T>.getElevation(): Float {
|
||||
return elevation.takeIf { it >= 0F } ?: 0F
|
||||
}
|
@ -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) {
|
||||
|
@ -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<String>()
|
||||
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<Int>, offset: Int = 0, block: ((Int) -> Unit)? = null) {
|
||||
setSelection(pref.get() - offset)
|
||||
|
||||
popup = makeSettingsPopup(pref, offset, block)
|
||||
setOnTouchListener(popup?.dragToOpenListener)
|
||||
setOnClickListener {
|
||||
popup?.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Enum<T>> bindToPreference(pref: Preference<T>, clazz: Class<T>) {
|
||||
val enumConstants = clazz.enumConstants
|
||||
enumConstants?.indexOf(pref.get())?.let { setSelection(it) }
|
||||
|
||||
popup = makeSettingsPopup(pref, clazz)
|
||||
setOnTouchListener(popup?.dragToOpenListener)
|
||||
setOnClickListener {
|
||||
popup?.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Enum<T>> makeSettingsPopup(preference: Preference<T>, clazz: Class<T>): PopupMenu {
|
||||
return createPopupMenu { pos ->
|
||||
onItemSelectedListener?.invoke(pos)
|
||||
|
||||
val enumConstants = clazz.enumConstants
|
||||
enumConstants?.get(pos)?.let { enumValue -> preference.set(enumValue) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSettingsPopup(preference: Preference<Int>, intValues: List<Int?>, block: ((Int) -> Unit)? = null): PopupMenu {
|
||||
return createPopupMenu { pos ->
|
||||
preference.set(intValues[pos] ?: 0)
|
||||
block?.invoke(pos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSettingsPopup(preference: Preference<Int>, 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
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="100%p"
|
||||
android:toYDelta="0" />
|
||||
|
||||
</set>
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="100%p" />
|
||||
|
||||
</set>
|
@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
|
||||
</vector>
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="8dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/center_guideline"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Title" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/details"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/dropdown_caret"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/center_guideline"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Details" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dropdown_caret"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="1dp"
|
||||
android:src="@drawable/ic_expand_more_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/details"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/details"
|
||||
app:tint="?android:attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/center_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -118,19 +118,6 @@
|
||||
app:srcCompat="@drawable/ic_screen_rotation_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_settings_legacy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_settings"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toStartOf="@+id/action_settings"
|
||||
app:layout_constraintStart_toEndOf="@id/action_rotation"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_settings_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_settings"
|
||||
android:layout_width="wrap_content"
|
||||
@ -139,7 +126,7 @@
|
||||
android:contentDescription="@string/action_settings"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/action_settings_legacy"
|
||||
app:layout_constraintStart_toEndOf="@id/action_rotation"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_settings_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pager_prefs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/pager_viewer"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/pager_nav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/pager_nav"
|
||||
app:title="@string/pref_viewer_nav" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/tapping_inverted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/invert_tapping_mode"
|
||||
app:title="@string/pref_read_with_tapping_inverted" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/scale_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/image_scale_type"
|
||||
app:title="@string/pref_image_scale_type" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/zoom_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/zoom_start"
|
||||
app:title="@string/pref_zoom_start" />
|
||||
|
||||
</LinearLayout>
|
@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/for_this_series_prefs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/pref_category_for_this_series"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/viewer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/viewers_selector"
|
||||
app:title="@string/pref_category_reading_mode" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/rotation_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:entries="@array/rotation_type"
|
||||
app:title="@string/rotation_type" />
|
||||
|
||||
<include
|
||||
android:id="@+id/pager_prefs_group"
|
||||
layout="@layout/reader_pager_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<include
|
||||
android:id="@+id/webtoon_prefs_group"
|
||||
layout="@layout/reader_webtoon_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/webtoon_prefs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/webtoon_viewer"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/webtoon_nav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/webtoon_nav"
|
||||
app:title="@string/pref_viewer_nav" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/tapping_inverted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/invert_tapping_mode"
|
||||
app:title="@string/pref_read_with_tapping_inverted" />
|
||||
|
||||
</LinearLayout>
|
@ -9,22 +9,6 @@
|
||||
<item>@string/vertical_plus_viewer</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="image_scale_type">
|
||||
<item>@string/scale_type_fit_screen</item>
|
||||
<item>@string/scale_type_stretch</item>
|
||||
<item>@string/scale_type_fit_width</item>
|
||||
<item>@string/scale_type_fit_height</item>
|
||||
<item>@string/scale_type_original_size</item>
|
||||
<item>@string/scale_type_smart_fit</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="zoom_start">
|
||||
<item>@string/zoom_start_automatic</item>
|
||||
<item>@string/zoom_start_left</item>
|
||||
<item>@string/zoom_start_right</item>
|
||||
<item>@string/zoom_start_center</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="rotation_type">
|
||||
<item>@string/label_default</item>
|
||||
<item>@string/rotation_free</item>
|
||||
@ -34,29 +18,4 @@
|
||||
<item>@string/rotation_force_landscape</item>
|
||||
<item>@string/rotation_reverse_portrait</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="invert_tapping_mode">
|
||||
<item>@string/tapping_inverted_none</item>
|
||||
<item>@string/tapping_inverted_horizontal</item>
|
||||
<item>@string/tapping_inverted_vertical</item>
|
||||
<item>@string/tapping_inverted_both</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pager_nav">
|
||||
<item>@string/label_default</item>
|
||||
<item>@string/l_nav</item>
|
||||
<item>@string/kindlish_nav</item>
|
||||
<item>@string/edge_nav</item>
|
||||
<item>@string/right_and_left_nav</item>
|
||||
<item>@string/disabled_nav</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="webtoon_nav">
|
||||
<item>@string/label_default</item>
|
||||
<item>@string/l_nav</item>
|
||||
<item>@string/kindlish_nav</item>
|
||||
<item>@string/edge_nav</item>
|
||||
<item>@string/right_and_left_nav</item>
|
||||
<item>@string/disabled_nav</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="MaterialSpinnerView">
|
||||
<attr name="title" format="reference|string"/>
|
||||
<attr name="android:entries"/>
|
||||
<attr name="summary" format="reference|string" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@ -1,6 +1,4 @@
|
||||
<resources>
|
||||
<dimen name="dialog_radius">8dp</dimen>
|
||||
|
||||
<dimen name="screen_edge_margin">16dp</dimen>
|
||||
|
||||
<dimen name="appwidget_background_radius">16dp</dimen>
|
||||
|
@ -20,33 +20,6 @@
|
||||
</style>
|
||||
|
||||
|
||||
<!--===========-->
|
||||
<!--BottomSheet-->
|
||||
<!--===========-->
|
||||
|
||||
<style name="ThemeOverlay.Tachiyomi.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/Widget.Tachiyomi.BottomSheet.Modal</item>
|
||||
<item name="android:windowAnimationStyle">@style/Animation.Tachiyomi.BottomSheetDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Tachiyomi.BottomSheet.Modal" parent="Widget.Material3.BottomSheet.Modal">
|
||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Tachiyomi.BottomSheet</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearanceOverlay.Tachiyomi.BottomSheet" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopRight">@dimen/dialog_radius</item>
|
||||
<item name="cornerSizeTopLeft">@dimen/dialog_radius</item>
|
||||
<item name="cornerSizeBottomRight">0dp</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.Tachiyomi.BottomSheetDialog" parent="Animation.AppCompat.Dialog">
|
||||
<item name="android:windowEnterAnimation">@anim/bottom_sheet_slide_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/bottom_sheet_slide_out</item>
|
||||
</style>
|
||||
|
||||
|
||||
<!--===============-->
|
||||
<!--Text Appearance-->
|
||||
<!--===============-->
|
||||
@ -71,18 +44,6 @@
|
||||
</style>
|
||||
|
||||
|
||||
<!--=================-->
|
||||
<!--Widgets.TabLayout-->
|
||||
<!--=================-->
|
||||
<style name="Widget.Tachiyomi.TabLayout" parent="Widget.Material3.TabLayout">
|
||||
<item name="tabGravity">center</item>
|
||||
<item name="tabInlineLabel">true</item>
|
||||
<item name="tabMinWidth">75dp</item>
|
||||
<item name="tabMode">scrollable</item>
|
||||
<item name="tabRippleColor">@color/ripple_toolbar_fainter</item>
|
||||
</style>
|
||||
|
||||
|
||||
<!--==============-->
|
||||
<!--Widgets.Switch-->
|
||||
<!--==============-->
|
||||
@ -94,16 +55,6 @@
|
||||
<item name="elevationOverlayEnabled">@bool/elevationOverlayEnabled</item>
|
||||
</style>
|
||||
|
||||
<!--==============-->
|
||||
<!--Widgets.Slider-->
|
||||
<!--==============-->
|
||||
<style name="Widget.Tachiyomi.Slider" parent="Widget.Material3.Slider">
|
||||
<item name="labelBehavior">gone</item>
|
||||
<item name="tickVisible">false</item>
|
||||
<item name="trackColorInactive">@color/slider_inactive_track</item>
|
||||
<item name="trackColorActive">@color/slider_active_track</item>
|
||||
</style>
|
||||
|
||||
|
||||
<!--============-->
|
||||
<!--FastScroller-->
|
||||
|
@ -64,17 +64,13 @@
|
||||
<item name="android:backgroundDimAmount">0.32</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="actionOverflowButtonStyle">@style/Theme.Tachiyomi.ActionButton.Overflow</item>
|
||||
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Tachiyomi</item>
|
||||
<item name="bottomSheetDialogTheme">@style/ThemeOverlay.Tachiyomi.BottomSheetDialog</item>
|
||||
<item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item>
|
||||
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
|
||||
<item name="toolbarStyle">@style/Widget.Material3.Toolbar.Surface</item>
|
||||
<item name="tabStyle">@style/Widget.Tachiyomi.TabLayout</item>
|
||||
<item name="switchStyle">@style/Widget.Tachiyomi.Switch</item>
|
||||
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
|
||||
<item name="switchPreferenceCompatStyle">@style/Widget.Tachiyomi.Switch</item>
|
||||
<item name="sliderStyle">@style/Widget.Tachiyomi.Slider</item>
|
||||
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
|
||||
|
||||
<!-- Preference text appearance -->
|
||||
|
@ -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 <T> evaluate(script: String): T = withIOContext {
|
||||
QuickJs.create().use {
|
||||
it.evaluate(script) as T
|
||||
|
@ -11,31 +11,26 @@ 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)
|
||||
}
|
||||
|
||||
private val baseClientBuilder: OkHttpClient.Builder
|
||||
get() {
|
||||
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)
|
||||
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
|
||||
|
||||
if (preferences.verboseLogging().get()) {
|
||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
@ -44,6 +39,8 @@ class NetworkHelper(
|
||||
builder.addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}
|
||||
|
||||
builder.addInterceptor(CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider))
|
||||
|
||||
when (preferences.dohProvider().get()) {
|
||||
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
||||
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
||||
@ -59,17 +56,15 @@ class NetworkHelper(
|
||||
PREF_DOH_SHECAN -> builder.dohShecan()
|
||||
}
|
||||
|
||||
return builder
|
||||
builder.build()
|
||||
}
|
||||
|
||||
val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
|
||||
|
||||
/**
|
||||
* @deprecated Since extension-lib 1.5
|
||||
*/
|
||||
@Deprecated("The regular client handles Cloudflare by default")
|
||||
@Suppress("UNUSED")
|
||||
val cloudflareClient by lazy {
|
||||
client.newBuilder()
|
||||
.addInterceptor(cloudflareInterceptor)
|
||||
.build()
|
||||
}
|
||||
val cloudflareClient: OkHttpClient = client
|
||||
|
||||
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
|
||||
}
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -388,7 +388,7 @@
|
||||
<string name="white_background">White</string>
|
||||
<string name="gray_background">Gray</string>
|
||||
<string name="black_background">Black</string>
|
||||
<string name="automatic_background">Automatic</string>
|
||||
<string name="automatic_background">Auto</string>
|
||||
<string name="pref_viewer_type">Default reading mode</string>
|
||||
<string name="l_nav">L shaped</string>
|
||||
<string name="kindlish_nav">Kindle-ish</string>
|
||||
@ -485,7 +485,6 @@
|
||||
<string name="action_track">Track</string>
|
||||
|
||||
<!-- Browse section -->
|
||||
<string name="pref_search_pinned_sources_only">Only search pinned sources in global search</string>
|
||||
<string name="pref_hide_in_library_items">Hide entries already in library</string>
|
||||
|
||||
<!-- Backup section -->
|
||||
@ -660,6 +659,7 @@
|
||||
<string name="latest">Latest</string>
|
||||
<string name="popular">Popular</string>
|
||||
<string name="browse">Browse</string>
|
||||
<string name="has_results">Has results</string>
|
||||
<string name="local_source_help_guide">Local source guide</string>
|
||||
<string name="no_pinned_sources">You have no pinned sources</string>
|
||||
<string name="chapter_not_found">Chapter not found</string>
|
||||
|
@ -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),
|
||||
) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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<String>,
|
||||
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 = {},
|
||||
)
|
||||
}
|
||||
}
|
@ -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)),
|
||||
|
Loading…
Reference in New Issue
Block a user