Merge branch 'master' into sync-part-1

This commit is contained in:
KaiserBh 2023-07-16 20:35:59 +10:00 committed by GitHub
commit abe69206dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 641 additions and 872 deletions

View File

@ -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),

View File

@ -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)
}

View File

@ -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,

View File

@ -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) {

View File

@ -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),

View File

@ -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(),

View File

@ -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) },
)
}
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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(

View File

@ -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()) }
}
}

View File

@ -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,
)

View File

@ -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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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()

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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
}
}

View File

@ -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,
)
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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-->

View File

@ -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 -->

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -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>

View File

@ -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),
) {

View File

@ -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)
}
}

View File

@ -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 = {},
)
}
}

View File

@ -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)),