mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-24 13:48:55 +03:00
parent
61256a22fd
commit
e7b1066e3c
82 changed files with 1486 additions and 1496 deletions
|
@ -3,24 +3,24 @@ package eu.kanade.domain.entries.anime.model
|
|||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
val Anime.downloadedFilter: TriStateFilter
|
||||
val Anime.downloadedFilter: TriState
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Anime.EPISODE_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
Anime.EPISODE_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
}
|
||||
fun Anime.episodesFiltered(): Boolean {
|
||||
return unseenFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
return unseenFilter != TriState.DISABLED ||
|
||||
downloadedFilter != TriState.DISABLED ||
|
||||
bookmarkedFilter != TriState.DISABLED
|
||||
}
|
||||
fun Anime.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
|
|
|
@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -20,19 +20,19 @@ val Manga.readingModeType: Long
|
|||
val Manga.orientationType: Long
|
||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||
|
||||
val Manga.downloadedFilter: TriStateFilter
|
||||
val Manga.downloadedFilter: TriState
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
}
|
||||
fun Manga.chaptersFiltered(): Boolean {
|
||||
return unreadFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
return unreadFilter != TriState.DISABLED ||
|
||||
downloadedFilter != TriState.DISABLED ||
|
||||
bookmarkedFilter != TriState.DISABLED
|
||||
}
|
||||
fun Manga.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
|
|
|
@ -37,9 +37,6 @@ class SourcePreferences(
|
|||
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
||||
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||
|
||||
fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
|
||||
fun searchPinnedMangaSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
||||
|
||||
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false)
|
||||
|
||||
fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
|
|
|
@ -61,12 +61,12 @@ fun BrowseAnimeSourceContent(
|
|||
if (animeList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = getErrorMessage(errorState),
|
||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
||||
actionLabel = context.getString(R.string.action_retry),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||
SnackbarResult.ActionPerformed -> animeList.refresh()
|
||||
SnackbarResult.ActionPerformed -> animeList.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
package eu.kanade.presentation.browse.anime
|
||||
|
||||
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.Divider
|
||||
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
|
||||
|
@ -17,7 +32,8 @@ import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchCardRow
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.GlobalAnimeSearchState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.GlobalAnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
@ -25,10 +41,13 @@ import tachiyomi.presentation.core.components.material.padding
|
|||
|
||||
@Composable
|
||||
fun GlobalAnimeSearchScreen(
|
||||
state: GlobalAnimeSearchState,
|
||||
state: GlobalAnimeSearchScreenModel.State,
|
||||
items: Map<AnimeCatalogueSource, AnimeSearchItemResult>,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
|
@ -36,6 +55,7 @@ fun GlobalAnimeSearchScreen(
|
|||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||
GlobalSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
|
@ -45,10 +65,68 @@ fun GlobalAnimeSearchScreen(
|
|||
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 == AnimeSourceFilter.PinnedOnly,
|
||||
onClick = { onChangeSearchFilter(AnimeSourceFilter.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 == AnimeSourceFilter.All,
|
||||
onClick = { onChangeSearchFilter(AnimeSourceFilter.All) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DoneAll,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.all))
|
||||
},
|
||||
)
|
||||
|
||||
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 ->
|
||||
GlobalAnimeSearchContent(
|
||||
items = state.items,
|
||||
items = items,
|
||||
contentPadding = paddingValues,
|
||||
getAnime = getAnime,
|
||||
onClickSource = onClickSource,
|
||||
|
|
|
@ -61,12 +61,12 @@ fun BrowseSourceContent(
|
|||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = getErrorMessage(errorState),
|
||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
||||
actionLabel = context.getString(R.string.action_retry),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||
SnackbarResult.ActionPerformed -> mangaList.refresh()
|
||||
SnackbarResult.ActionPerformed -> mangaList.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
package eu.kanade.presentation.browse.manga
|
||||
|
||||
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.Divider
|
||||
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,8 +31,9 @@ import eu.kanade.presentation.browse.GlobalSearchToolbar
|
|||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchCardRow
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.GlobalMangaSearchState
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.GlobalMangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
@ -25,10 +41,13 @@ import tachiyomi.presentation.core.components.material.padding
|
|||
|
||||
@Composable
|
||||
fun GlobalMangaSearchScreen(
|
||||
state: GlobalMangaSearchState,
|
||||
state: GlobalMangaSearchScreenModel.State,
|
||||
items: Map<CatalogueSource, MangaSearchItemResult>,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (MangaSourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
|
@ -36,6 +55,7 @@ fun GlobalMangaSearchScreen(
|
|||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||
GlobalSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
|
@ -45,10 +65,68 @@ fun GlobalMangaSearchScreen(
|
|||
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 == MangaSourceFilter.PinnedOnly,
|
||||
onClick = { onChangeSearchFilter(MangaSourceFilter.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 == MangaSourceFilter.All,
|
||||
onClick = { onChangeSearchFilter(MangaSourceFilter.All) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DoneAll,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.all))
|
||||
},
|
||||
)
|
||||
|
||||
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,
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
package eu.kanade.presentation.components
|
||||
|
||||
import android.view.MotionEvent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AddCircle
|
||||
import androidx.compose.material.icons.outlined.RemoveCircle
|
||||
import androidx.compose.material.icons.rounded.CheckBox
|
||||
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||
import androidx.compose.material.icons.rounded.DisabledByDefault
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.presentation.core.components.SettingsItemsPaddings
|
||||
|
||||
@Composable
|
||||
fun TriStateItem(
|
||||
label: String,
|
||||
state: TriStateFilter,
|
||||
enabled: Boolean = true,
|
||||
onClick: ((TriStateFilter) -> Unit)?,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
enabled = enabled && onClick != null,
|
||||
onClick = {
|
||||
when (state) {
|
||||
TriStateFilter.DISABLED -> onClick?.invoke(TriStateFilter.ENABLED_IS)
|
||||
TriStateFilter.ENABLED_IS -> onClick?.invoke(TriStateFilter.ENABLED_NOT)
|
||||
TriStateFilter.ENABLED_NOT -> onClick?.invoke(TriStateFilter.DISABLED)
|
||||
}
|
||||
},
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = SettingsItemsPaddings.Horizontal,
|
||||
vertical = SettingsItemsPaddings.Vertical,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled
|
||||
|
||||
Icon(
|
||||
imageVector = when (state) {
|
||||
TriStateFilter.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank
|
||||
TriStateFilter.ENABLED_IS -> Icons.Rounded.CheckBox
|
||||
TriStateFilter.ENABLED_NOT -> Icons.Rounded.DisabledByDefault
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = if (!enabled || state == TriStateFilter.DISABLED) {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha)
|
||||
} else {
|
||||
when (onClick) {
|
||||
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled)
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> SelectItem(
|
||||
label: String,
|
||||
options: Array<T>,
|
||||
selectedIndex: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
onSelect: (Int) -> Unit,
|
||||
toString: (T) -> String = { it.toString() },
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
modifier = modifier,
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = SettingsItemsPaddings.Horizontal,
|
||||
vertical = SettingsItemsPaddings.Vertical,
|
||||
),
|
||||
label = { Text(text = label) },
|
||||
value = toString(options[selectedIndex]),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded,
|
||||
)
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
options.forEachIndexed { index, option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(toString(option)) },
|
||||
onClick = {
|
||||
onSelect(index)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RepeatingIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
maxDelayMillis: Long = 750,
|
||||
minDelayMillis: Long = 5,
|
||||
delayDecayFactor: Float = .25f,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val currentClickListener by rememberUpdatedState(onClick)
|
||||
var pressed by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
modifier = modifier.pointerInteropFilter {
|
||||
pressed = when (it.action) {
|
||||
MotionEvent.ACTION_DOWN -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
onClick = {},
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource,
|
||||
content = content,
|
||||
)
|
||||
|
||||
LaunchedEffect(pressed, enabled) {
|
||||
var currentDelayMillis = maxDelayMillis
|
||||
|
||||
while (enabled && pressed) {
|
||||
currentClickListener()
|
||||
delay(currentDelayMillis)
|
||||
currentDelayMillis =
|
||||
(currentDelayMillis - (currentDelayMillis * delayDecayFactor))
|
||||
.toLong().coerceAtLeast(minDelayMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OutlinedNumericChooser(
|
||||
label: String,
|
||||
placeholder: String,
|
||||
suffix: String,
|
||||
value: Int,
|
||||
step: Int,
|
||||
min: Int? = null,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
) {
|
||||
var currentValue = value
|
||||
|
||||
val updateValue: (Boolean) -> Unit = {
|
||||
currentValue += if (it) step else -step
|
||||
|
||||
if (min != null) currentValue = if (currentValue < min) min else currentValue
|
||||
|
||||
onValueChanged(currentValue)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RepeatingIconButton(
|
||||
onClick = { updateValue(false) },
|
||||
) { Icon(imageVector = Icons.Outlined.RemoveCircle, contentDescription = null) }
|
||||
|
||||
OutlinedTextField(
|
||||
value = "%d".format(currentValue),
|
||||
modifier = Modifier.widthIn(min = 140.dp),
|
||||
|
||||
onValueChange = {
|
||||
// Don't allow multiple decimal points, non-numeric characters, or leading zeros
|
||||
currentValue = it.trim().replace(Regex("[^-\\d.]"), "").toIntOrNull()
|
||||
?: currentValue
|
||||
onValueChanged(currentValue)
|
||||
},
|
||||
|
||||
label = { Text(text = label) },
|
||||
placeholder = { Text(text = placeholder) },
|
||||
suffix = { Text(text = suffix) },
|
||||
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
)
|
||||
|
||||
RepeatingIconButton(
|
||||
onClick = { updateValue(true) },
|
||||
) { Icon(imageVector = Icons.Outlined.AddCircle, contentDescription = null) }
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
|
@ -46,6 +47,7 @@ fun TabbedDialog(
|
|||
onOverflowMenuClicked: (() -> Unit)? = null,
|
||||
overflowIcon: ImageVector? = null,
|
||||
hideSystemBars: Boolean = false,
|
||||
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
||||
content: @Composable (Int) -> Unit,
|
||||
) {
|
||||
AdaptiveSheet(
|
||||
|
@ -53,7 +55,6 @@ fun TabbedDialog(
|
|||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState { tabTitles.size }
|
||||
|
||||
Column {
|
||||
Row {
|
||||
|
|
|
@ -26,20 +26,20 @@ import eu.kanade.domain.entries.anime.model.downloadedFilter
|
|||
import eu.kanade.domain.entries.anime.model.forceDownloaded
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
|
||||
@Composable
|
||||
fun EpisodeSettingsDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
anime: Anime? = null,
|
||||
onDownloadFilterChanged: (TriStateFilter) -> Unit,
|
||||
onUnseenFilterChanged: (TriStateFilter) -> Unit,
|
||||
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
|
||||
onDownloadFilterChanged: (TriState) -> Unit,
|
||||
onUnseenFilterChanged: (TriState) -> Unit,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
onSortModeChanged: (Long) -> Unit,
|
||||
onDisplayModeChanged: (Long) -> Unit,
|
||||
onSetAsDefault: (applyToExistingAnime: Boolean) -> Unit,
|
||||
|
@ -77,11 +77,11 @@ fun EpisodeSettingsDialog(
|
|||
when (page) {
|
||||
0 -> {
|
||||
FilterPage(
|
||||
downloadFilter = anime?.downloadedFilter ?: TriStateFilter.DISABLED,
|
||||
downloadFilter = anime?.downloadedFilter ?: TriState.DISABLED,
|
||||
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { anime?.forceDownloaded() == true },
|
||||
unseenFilter = anime?.unseenFilter ?: TriStateFilter.DISABLED,
|
||||
unseenFilter = anime?.unseenFilter ?: TriState.DISABLED,
|
||||
onUnseenFilterChanged = onUnseenFilterChanged,
|
||||
bookmarkedFilter = anime?.bookmarkedFilter ?: TriStateFilter.DISABLED,
|
||||
bookmarkedFilter = anime?.bookmarkedFilter ?: TriState.DISABLED,
|
||||
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
||||
)
|
||||
}
|
||||
|
@ -105,12 +105,12 @@ fun EpisodeSettingsDialog(
|
|||
|
||||
@Composable
|
||||
private fun FilterPage(
|
||||
downloadFilter: TriStateFilter,
|
||||
onDownloadFilterChanged: ((TriStateFilter) -> Unit)?,
|
||||
unseenFilter: TriStateFilter,
|
||||
onUnseenFilterChanged: (TriStateFilter) -> Unit,
|
||||
bookmarkedFilter: TriStateFilter,
|
||||
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
|
||||
downloadFilter: TriState,
|
||||
onDownloadFilterChanged: ((TriState) -> Unit)?,
|
||||
unseenFilter: TriState,
|
||||
onUnseenFilterChanged: (TriState) -> Unit,
|
||||
bookmarkedFilter: TriState,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
) {
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
|
|
|
@ -26,20 +26,20 @@ import eu.kanade.domain.entries.manga.model.downloadedFilter
|
|||
import eu.kanade.domain.entries.manga.model.forceDownloaded
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
|
||||
@Composable
|
||||
fun ChapterSettingsDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
manga: Manga? = null,
|
||||
onDownloadFilterChanged: (TriStateFilter) -> Unit,
|
||||
onUnreadFilterChanged: (TriStateFilter) -> Unit,
|
||||
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
|
||||
onDownloadFilterChanged: (TriState) -> Unit,
|
||||
onUnreadFilterChanged: (TriState) -> Unit,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
onSortModeChanged: (Long) -> Unit,
|
||||
onDisplayModeChanged: (Long) -> Unit,
|
||||
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
||||
|
@ -77,11 +77,11 @@ fun ChapterSettingsDialog(
|
|||
when (page) {
|
||||
0 -> {
|
||||
FilterPage(
|
||||
downloadFilter = manga?.downloadedFilter ?: TriStateFilter.DISABLED,
|
||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
|
||||
unreadFilter = manga?.unreadFilter ?: TriStateFilter.DISABLED,
|
||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriStateFilter.DISABLED,
|
||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
||||
)
|
||||
}
|
||||
|
@ -105,12 +105,12 @@ fun ChapterSettingsDialog(
|
|||
|
||||
@Composable
|
||||
private fun FilterPage(
|
||||
downloadFilter: TriStateFilter,
|
||||
onDownloadFilterChanged: ((TriStateFilter) -> Unit)?,
|
||||
unreadFilter: TriStateFilter,
|
||||
onUnreadFilterChanged: (TriStateFilter) -> Unit,
|
||||
bookmarkedFilter: TriStateFilter,
|
||||
onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
|
||||
downloadFilter: TriState,
|
||||
onDownloadFilterChanged: ((TriState) -> Unit)?,
|
||||
unreadFilter: TriState,
|
||||
onUnreadFilterChanged: (TriState) -> Unit,
|
||||
bookmarkedFilter: TriState,
|
||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||
) {
|
||||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
|
|
|
@ -7,21 +7,18 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.library.anime.AnimeLibrarySettingsScreenModel
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.library.anime.model.AnimeLibrarySort
|
||||
import tachiyomi.domain.library.anime.model.sort
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
@ -31,6 +28,7 @@ import tachiyomi.presentation.core.components.HeadingItem
|
|||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
|
||||
@Composable
|
||||
fun AnimeLibrarySettingsDialog(
|
||||
|
@ -76,7 +74,7 @@ private fun ColumnScope.FilterPage(
|
|||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
state = if (downloadedOnly) {
|
||||
TriStateFilter.ENABLED_IS
|
||||
TriState.ENABLED_IS
|
||||
} else {
|
||||
filterDownloaded
|
||||
},
|
||||
|
|
|
@ -7,21 +7,18 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.library.manga.MangaLibrarySettingsScreenModel
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.library.manga.model.MangaLibrarySort
|
||||
import tachiyomi.domain.library.manga.model.sort
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
@ -31,6 +28,7 @@ import tachiyomi.presentation.core.components.HeadingItem
|
|||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
|
||||
@Composable
|
||||
fun MangaLibrarySettingsDialog(
|
||||
|
@ -76,7 +74,7 @@ private fun ColumnScope.FilterPage(
|
|||
TriStateItem(
|
||||
label = stringResource(R.string.label_downloaded),
|
||||
state = if (downloadedOnly) {
|
||||
TriStateFilter.ENABLED_IS
|
||||
TriState.ENABLED_IS
|
||||
} else {
|
||||
filterDownloaded
|
||||
},
|
||||
|
|
|
@ -87,7 +87,7 @@ internal fun PreferenceItem(
|
|||
min = item.min,
|
||||
max = item.max,
|
||||
value = item.value,
|
||||
valueText = item.value.toString(),
|
||||
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||
onChange = {
|
||||
scope.launch {
|
||||
item.onValueChanged(it)
|
||||
|
|
|
@ -29,14 +29,6 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||
Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.label_sources),
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = sourcePreferences.searchPinnedAnimeSourcesOnly(),
|
||||
title = stringResource(R.string.pref_search_pinned_anime_sources_only),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = sourcePreferences.searchPinnedMangaSourcesOnly(),
|
||||
title = stringResource(R.string.pref_search_pinned_manga_sources_only),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = sourcePreferences.hideInAnimeLibraryItems(),
|
||||
title = stringResource(R.string.pref_hide_in_anime_library_items),
|
||||
|
|
|
@ -394,7 +394,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = libraryPreferences.swipeChapterStartAction(),
|
||||
title = stringResource(R.string.pref_chapter_swipe_end),
|
||||
title = stringResource(R.string.pref_chapter_swipe_start),
|
||||
entries = mapOf(
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
|
||||
|
@ -404,7 +404,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = libraryPreferences.swipeChapterEndAction(),
|
||||
title = stringResource(R.string.pref_chapter_swipe_start),
|
||||
title = stringResource(R.string.pref_chapter_swipe_end),
|
||||
entries = mapOf(
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
|
||||
|
@ -425,7 +425,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = libraryPreferences.swipeEpisodeStartAction(),
|
||||
title = stringResource(R.string.pref_episode_swipe_end),
|
||||
title = stringResource(R.string.pref_episode_swipe_start),
|
||||
entries = mapOf(
|
||||
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
|
||||
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode),
|
||||
|
@ -435,7 +435,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = libraryPreferences.swipeEpisodeEndAction(),
|
||||
title = stringResource(R.string.pref_episode_swipe_start),
|
||||
title = stringResource(R.string.pref_episode_swipe_end),
|
||||
entries = mapOf(
|
||||
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
|
||||
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode),
|
||||
|
|
|
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.NumberFormat
|
||||
|
||||
object SettingsReaderScreen : SearchableSettings {
|
||||
|
||||
|
@ -252,11 +253,15 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
|
||||
@Composable
|
||||
private fun getWebtoonGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
||||
|
||||
val navModePref = readerPreferences.navigationModeWebtoon()
|
||||
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon()
|
||||
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding()
|
||||
|
||||
val navMode by navModePref.collectAsState()
|
||||
val dualPageSplit by dualPageSplitPref.collectAsState()
|
||||
val webtoonSidePadding by webtoonSidePaddingPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.webtoon_viewer),
|
||||
|
@ -279,17 +284,16 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
),
|
||||
enabled = navMode != 5,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = readerPreferences.webtoonSidePadding(),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = webtoonSidePadding,
|
||||
title = stringResource(R.string.pref_webtoon_side_padding),
|
||||
entries = mapOf(
|
||||
0 to stringResource(R.string.webtoon_side_padding_0),
|
||||
5 to stringResource(R.string.webtoon_side_padding_5),
|
||||
10 to stringResource(R.string.webtoon_side_padding_10),
|
||||
15 to stringResource(R.string.webtoon_side_padding_15),
|
||||
20 to stringResource(R.string.webtoon_side_padding_20),
|
||||
25 to stringResource(R.string.webtoon_side_padding_25),
|
||||
),
|
||||
subtitle = numberFormat.format(webtoonSidePadding / 100f),
|
||||
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
||||
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
||||
onValueChanged = {
|
||||
webtoonSidePaddingPref.set(it)
|
||||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = readerPreferences.readerHideThreshold(),
|
||||
|
|
|
@ -4,9 +4,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.SelectItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.presentation.core.components.SelectItem
|
||||
|
||||
@Composable
|
||||
fun SelectStorageCategory(
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
package eu.kanade.presentation.reader.settings
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.SliderItem
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
||||
val colorFilterModes = buildList {
|
||||
addAll(
|
||||
listOf(
|
||||
R.string.label_default,
|
||||
R.string.filter_mode_multiply,
|
||||
R.string.filter_mode_screen,
|
||||
),
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
addAll(
|
||||
listOf(
|
||||
R.string.filter_mode_overlay,
|
||||
R.string.filter_mode_lighten,
|
||||
R.string.filter_mode_darken,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.map { stringResource(it) }
|
||||
|
||||
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_custom_brightness),
|
||||
checked = customBrightness,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::customBrightness)
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Sets the brightness of the screen. Range is [-75, 100].
|
||||
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
||||
* From 1 to 100 it sets that value as brightness.
|
||||
* 0 sets system brightness and hides the overlay.
|
||||
*/
|
||||
if (customBrightness) {
|
||||
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
|
||||
SliderItem(
|
||||
label = stringResource(R.string.pref_custom_brightness),
|
||||
min = -75,
|
||||
max = 100,
|
||||
value = customBrightnessValue,
|
||||
valueText = customBrightnessValue.toString(),
|
||||
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
|
||||
)
|
||||
}
|
||||
|
||||
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_custom_color_filter),
|
||||
checked = colorFilter,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::colorFilter)
|
||||
},
|
||||
)
|
||||
if (colorFilter) {
|
||||
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
||||
SliderItem(
|
||||
label = stringResource(R.string.color_filter_r_value),
|
||||
max = 255,
|
||||
value = colorFilterValue.red,
|
||||
valueText = colorFilterValue.red.toString(),
|
||||
onChange = { newRValue ->
|
||||
screenModel.preferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newRValue, RED_MASK, 16)
|
||||
}
|
||||
},
|
||||
)
|
||||
SliderItem(
|
||||
label = stringResource(R.string.color_filter_g_value),
|
||||
max = 255,
|
||||
value = colorFilterValue.green,
|
||||
valueText = colorFilterValue.green.toString(),
|
||||
onChange = { newGValue ->
|
||||
screenModel.preferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newGValue, GREEN_MASK, 8)
|
||||
}
|
||||
},
|
||||
)
|
||||
SliderItem(
|
||||
label = stringResource(R.string.color_filter_b_value),
|
||||
max = 255,
|
||||
value = colorFilterValue.blue,
|
||||
valueText = colorFilterValue.blue.toString(),
|
||||
onChange = { newBValue ->
|
||||
screenModel.preferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newBValue, BLUE_MASK, 0)
|
||||
}
|
||||
},
|
||||
)
|
||||
SliderItem(
|
||||
label = stringResource(R.string.color_filter_a_value),
|
||||
max = 255,
|
||||
value = colorFilterValue.alpha,
|
||||
valueText = colorFilterValue.alpha.toString(),
|
||||
onChange = { newAValue ->
|
||||
screenModel.preferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
val grayscale by screenModel.preferences.grayscale().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_grayscale),
|
||||
checked = grayscale,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::grayscale)
|
||||
},
|
||||
)
|
||||
val invertedColors by screenModel.preferences.invertedColors().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_inverted_colors),
|
||||
checked = invertedColors,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::invertedColors)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getColorValue(currentColor: Int, color: Int, mask: Long, bitShift: Int): Int {
|
||||
return (color shl bitShift) or (currentColor and mask.inv().toInt())
|
||||
}
|
||||
private const val ALPHA_MASK: Long = 0xFF000000
|
||||
private const val RED_MASK: Long = 0x00FF0000
|
||||
private const val GREEN_MASK: Long = 0x0000FF00
|
||||
private const val BLUE_MASK: Long = 0x000000FF
|
|
@ -0,0 +1,96 @@
|
|||
package eu.kanade.presentation.reader.settings
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
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
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||
// TODO: show this in a nicer way
|
||||
HeadingItem(R.string.pref_reader_theme)
|
||||
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
||||
listOf(
|
||||
R.string.black_background to 1,
|
||||
R.string.gray_background to 2,
|
||||
R.string.white_background to 0,
|
||||
R.string.automatic_background to 3,
|
||||
).map { (titleRes, theme) ->
|
||||
RadioItem(
|
||||
label = stringResource(titleRes),
|
||||
selected = readerTheme == theme,
|
||||
onClick = { screenModel.preferences.readerTheme().set(theme) },
|
||||
)
|
||||
}
|
||||
|
||||
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_show_page_number),
|
||||
checked = showPageNumber,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::showPageNumber)
|
||||
},
|
||||
)
|
||||
|
||||
val fullscreen by screenModel.preferences.fullscreen().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_fullscreen),
|
||||
checked = fullscreen,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::fullscreen)
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: hide if there's no cutout
|
||||
val cutoutShort by screenModel.preferences.cutoutShort().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_cutout_short),
|
||||
checked = cutoutShort,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::cutoutShort)
|
||||
},
|
||||
)
|
||||
|
||||
val keepScreenOn by screenModel.preferences.keepScreenOn().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_keep_screen_on),
|
||||
checked = keepScreenOn,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::keepScreenOn)
|
||||
},
|
||||
)
|
||||
|
||||
val readWithLongTap by screenModel.preferences.readWithLongTap().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_read_with_long_tap),
|
||||
checked = readWithLongTap,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::readWithLongTap)
|
||||
},
|
||||
)
|
||||
|
||||
val alwaysShowChapterTransition by screenModel.preferences.alwaysShowChapterTransition().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_always_show_chapter_transition),
|
||||
checked = alwaysShowChapterTransition,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::alwaysShowChapterTransition)
|
||||
},
|
||||
)
|
||||
|
||||
val pageTransitions by screenModel.preferences.pageTransitions().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_page_transitions),
|
||||
checked = pageTransitions,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::pageTransitions)
|
||||
},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package eu.kanade.presentation.reader.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
|
||||
@Composable
|
||||
fun ReaderSettingsDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onShowMenus: () -> Unit,
|
||||
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),
|
||||
stringResource(R.string.custom_filter),
|
||||
)
|
||||
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()
|
||||
onShowMenus()
|
||||
},
|
||||
tabTitles = tabTitles,
|
||||
pagerState = pagerState,
|
||||
) { page ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
when (page) {
|
||||
0 -> ReadingModePage(screenModel)
|
||||
1 -> GeneralPage(screenModel)
|
||||
2 -> ColorFilterPage(screenModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package eu.kanade.presentation.reader.settings
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import java.text.NumberFormat
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||
HeadingItem(R.string.pref_category_for_this_series)
|
||||
|
||||
// Reading mode
|
||||
// Rotation type
|
||||
|
||||
// if (pager)
|
||||
PagerViewerSettings(screenModel)
|
||||
|
||||
WebtoonViewerSettings(screenModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||
HeadingItem(R.string.pager_viewer)
|
||||
|
||||
// Tap zones
|
||||
// Invert tap zones
|
||||
// Scale type
|
||||
// Zoom start position
|
||||
|
||||
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_crop_borders),
|
||||
checked = cropBorders,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::cropBorders)
|
||||
},
|
||||
)
|
||||
|
||||
val landscapeZoom by screenModel.preferences.landscapeZoom().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_landscape_zoom),
|
||||
checked = landscapeZoom,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::landscapeZoom)
|
||||
},
|
||||
)
|
||||
|
||||
val navigateToPan by screenModel.preferences.navigateToPan().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_navigate_pan),
|
||||
checked = navigateToPan,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::navigateToPan)
|
||||
},
|
||||
)
|
||||
|
||||
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_dual_page_split),
|
||||
checked = dualPageSplitPaged,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageSplitPaged)
|
||||
},
|
||||
)
|
||||
|
||||
if (dualPageSplitPaged) {
|
||||
val dualPageInvertPaged by screenModel.preferences.dualPageInvertPaged().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_dual_page_invert),
|
||||
checked = dualPageInvertPaged,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageInvertPaged)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_page_rotate),
|
||||
checked = dualPageRotateToFit,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFit)
|
||||
},
|
||||
)
|
||||
|
||||
if (dualPageRotateToFit) {
|
||||
val dualPageRotateToFitInvert by screenModel.preferences.dualPageRotateToFitInvert().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_page_rotate_invert),
|
||||
checked = dualPageRotateToFitInvert,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFitInvert)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
||||
|
||||
HeadingItem(R.string.webtoon_viewer)
|
||||
|
||||
// TODO: Tap zones
|
||||
// TODO: Invert tap zones
|
||||
|
||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||
SliderItem(
|
||||
label = stringResource(R.string.pref_webtoon_side_padding),
|
||||
min = ReaderPreferences.WEBTOON_PADDING_MIN,
|
||||
max = ReaderPreferences.WEBTOON_PADDING_MAX,
|
||||
value = webtoonSidePadding,
|
||||
valueText = numberFormat.format(webtoonSidePadding / 100f),
|
||||
onChange = {
|
||||
screenModel.preferences.webtoonSidePadding().set(it)
|
||||
},
|
||||
)
|
||||
|
||||
val cropBordersWebtoon by screenModel.preferences.cropBordersWebtoon().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_crop_borders),
|
||||
checked = cropBordersWebtoon,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::cropBordersWebtoon)
|
||||
},
|
||||
)
|
||||
|
||||
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_dual_page_split),
|
||||
checked = dualPageSplitWebtoon,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageSplitWebtoon)
|
||||
},
|
||||
)
|
||||
|
||||
if (dualPageSplitWebtoon) {
|
||||
val dualPageInvertWebtoon by screenModel.preferences.dualPageInvertWebtoon()
|
||||
.collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_dual_page_invert),
|
||||
checked = dualPageInvertWebtoon,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::dualPageInvertWebtoon)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!isReleaseBuildType) {
|
||||
val longStripSplitWebtoon by screenModel.preferences.longStripSplitWebtoon()
|
||||
.collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_long_strip_split),
|
||||
checked = longStripSplitWebtoon,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::longStripSplitWebtoon)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val webtoonDoubleTapZoomEnabled by screenModel.preferences.webtoonDoubleTapZoomEnabled().collectAsState()
|
||||
CheckboxItem(
|
||||
label = stringResource(R.string.pref_double_tap_zoom),
|
||||
checked = webtoonDoubleTapZoomEnabled,
|
||||
onClick = {
|
||||
screenModel.togglePreference(ReaderPreferences::webtoonDoubleTapZoomEnabled)
|
||||
},
|
||||
)
|
||||
}
|
|
@ -23,12 +23,11 @@ import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
|||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.getEnum
|
||||
import tachiyomi.domain.backup.service.BackupPreferences
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
object Migrations {
|
||||
|
@ -434,12 +433,12 @@ object Migrations {
|
|||
remove(key)
|
||||
|
||||
val newValue = when (pref.get()) {
|
||||
1 -> TriStateFilter.ENABLED_IS
|
||||
2 -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
1 -> TriState.ENABLED_IS
|
||||
2 -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
|
||||
preferenceStore.getEnum("${key}_v2", TriStateFilter.DISABLED).set(newValue)
|
||||
preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,12 @@ internal object AnimeExtensionLoader {
|
|||
const val LIB_VERSION_MIN = 12
|
||||
const val LIB_VERSION_MAX = 15
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// jmir1's key
|
||||
private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c"
|
||||
|
@ -49,7 +54,7 @@ internal object AnimeExtensionLoader {
|
|||
/**
|
||||
* 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 AnimeExtensionLoader {
|
|||
fun loadExtensions(context: Context): List<AnimeLoadResult> {
|
||||
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 AnimeExtensionLoader {
|
|||
return AnimeLoadResult.Error
|
||||
}
|
||||
|
||||
val signatureHash = getSignatureHash(pkgInfo)
|
||||
val signatureHash = getSignatureHash(context, pkgInfo)
|
||||
|
||||
if (signatureHash == null) {
|
||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||
|
@ -221,12 +225,8 @@ internal object AnimeExtensionLoader {
|
|||
*
|
||||
* @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()) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,12 @@ internal object MangaExtensionLoader {
|
|||
const val LIB_VERSION_MIN = 1.2
|
||||
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 MangaExtensionLoader {
|
|||
/**
|
||||
* 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 MangaExtensionLoader {
|
|||
fun loadMangaExtensions(context: Context): List<MangaLoadResult> {
|
||||
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 MangaExtensionLoader {
|
|||
return MangaLoadResult.Error
|
||||
}
|
||||
|
||||
val signatureHash = getSignatureHash(pkgInfo)
|
||||
val signatureHash = getSignatureHash(context, pkgInfo)
|
||||
|
||||
if (signatureHash == null) {
|
||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||
|
@ -221,12 +225,8 @@ internal object MangaExtensionLoader {
|
|||
*
|
||||
* @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 MigrateAnimeDialogScreenModel(
|
|||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val isMigrating: Boolean = false,
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.anime.MigrateAnimeSearchScreen
|
|||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreen
|
||||
|
||||
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||
class MigrateAnimeSearchScreen(private val animeId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -18,17 +18,17 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.components.SelectItem
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.CollapsibleBox
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.SelectItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TextItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
|
||||
@Composable
|
||||
fun SourceFilterAnimeDialog(
|
||||
|
@ -165,19 +165,19 @@ private fun FilterItem(filter: AnimeFilter<*>, onUpdate: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Int.toTriStateFilter(): TriStateFilter {
|
||||
private fun Int.toTriStateFilter(): TriState {
|
||||
return when (this) {
|
||||
AnimeFilter.TriState.STATE_IGNORE -> TriStateFilter.DISABLED
|
||||
AnimeFilter.TriState.STATE_INCLUDE -> TriStateFilter.ENABLED_IS
|
||||
AnimeFilter.TriState.STATE_EXCLUDE -> TriStateFilter.ENABLED_NOT
|
||||
AnimeFilter.TriState.STATE_IGNORE -> TriState.DISABLED
|
||||
AnimeFilter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS
|
||||
AnimeFilter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT
|
||||
else -> throw IllegalStateException("Unknown TriState state: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun TriStateFilter.toTriStateInt(): Int {
|
||||
private fun TriState.toTriStateInt(): Int {
|
||||
return when (this) {
|
||||
TriStateFilter.DISABLED -> AnimeFilter.TriState.STATE_IGNORE
|
||||
TriStateFilter.ENABLED_IS -> AnimeFilter.TriState.STATE_INCLUDE
|
||||
TriStateFilter.ENABLED_NOT -> AnimeFilter.TriState.STATE_EXCLUDE
|
||||
TriState.DISABLED -> AnimeFilter.TriState.STATE_IGNORE
|
||||
TriState.ENABLED_IS -> AnimeFilter.TriState.STATE_INCLUDE
|
||||
TriState.ENABLED_NOT -> AnimeFilter.TriState.STATE_EXCLUDE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,16 +68,7 @@ abstract class AnimeSearchScreenModel<T>(
|
|||
val enabledSources = getEnabledSources()
|
||||
|
||||
if (filter.isEmpty()) {
|
||||
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedAnimeSourcesOnly().get()
|
||||
val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
|
||||
|
||||
return enabledSources.filter {
|
||||
if (shouldSearchPinnedOnly) {
|
||||
"${it.id}" in pinnedSources
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
return enabledSources
|
||||
}
|
||||
|
||||
return extensionManager.installedExtensionsFlow.value
|
||||
|
@ -136,6 +127,11 @@ abstract class AnimeSearchScreenModel<T>(
|
|||
}
|
||||
}
|
||||
|
||||
enum class AnimeSourceFilter {
|
||||
All,
|
||||
PinnedOnly,
|
||||
}
|
||||
|
||||
sealed class AnimeSearchItemResult {
|
||||
object Loading : AnimeSearchItemResult()
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ class GlobalAnimeSearchScreen(
|
|||
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 GlobalAnimeSearchScreen(
|
|||
} else {
|
||||
GlobalAnimeSearchScreen(
|
||||
state = state,
|
||||
items = filteredSources,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
getAnime = { screenModel.getAnime(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.anime.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.animesource.AnimeCatalogueSource
|
||||
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.anime.service.AnimeSourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -15,8 +20,8 @@ class GlobalAnimeSearchScreenModel(
|
|||
preferences: BasePreferences = Injekt.get(),
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
||||
) : AnimeSearchScreenModel<GlobalAnimeSearchState>(
|
||||
GlobalAnimeSearchState(
|
||||
) : AnimeSearchScreenModel<GlobalAnimeSearchScreenModel.State>(
|
||||
State(
|
||||
searchQuery = initialQuery,
|
||||
),
|
||||
) {
|
||||
|
@ -24,6 +29,13 @@ class GlobalAnimeSearchScreenModel(
|
|||
val incognitoMode = preferences.incognitoMode()
|
||||
val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource()
|
||||
|
||||
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()) {
|
||||
|
@ -37,6 +49,7 @@ class GlobalAnimeSearchScreenModel(
|
|||
val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
|
||||
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { mutableState.value.sourceFilter != AnimeSourceFilter.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})" }))
|
||||
|
@ -57,15 +70,29 @@ class GlobalAnimeSearchScreenModel(
|
|||
override fun getItems(): Map<AnimeCatalogueSource, AnimeSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class GlobalAnimeSearchState(
|
||||
fun setSourceFilter(filter: AnimeSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnimeSearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
||||
return !onlyShowHasResults || (this is AnimeSearchItemResult.Success && !this.isEmpty)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: AnimeSourceFilter = AnimeSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<AnimeCatalogueSource, AnimeSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is AnimeSearchItemResult.Loading }
|
||||
|
||||
val total: Int = items.size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 MigrateMangaDialogScreenModel(
|
|||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val isMigrating: Boolean = false,
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.manga.MigrateMangaSearchScreen
|
|||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.MangaScreen
|
||||
|
||||
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||
class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -16,17 +16,17 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.components.SelectItem
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.CollapsibleBox
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.SelectItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TextItem
|
||||
import tachiyomi.presentation.core.components.TriStateItem
|
||||
import tachiyomi.presentation.core.components.material.Button
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
|
||||
|
@ -165,19 +165,19 @@ private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Int.toTriStateFilter(): TriStateFilter {
|
||||
private fun Int.toTriStateFilter(): TriState {
|
||||
return when (this) {
|
||||
Filter.TriState.STATE_IGNORE -> TriStateFilter.DISABLED
|
||||
Filter.TriState.STATE_INCLUDE -> TriStateFilter.ENABLED_IS
|
||||
Filter.TriState.STATE_EXCLUDE -> TriStateFilter.ENABLED_NOT
|
||||
Filter.TriState.STATE_IGNORE -> TriState.DISABLED
|
||||
Filter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS
|
||||
Filter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT
|
||||
else -> throw IllegalStateException("Unknown TriState state: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun TriStateFilter.toTriStateInt(): Int {
|
||||
private fun TriState.toTriStateInt(): Int {
|
||||
return when (this) {
|
||||
TriStateFilter.DISABLED -> Filter.TriState.STATE_IGNORE
|
||||
TriStateFilter.ENABLED_IS -> Filter.TriState.STATE_INCLUDE
|
||||
TriStateFilter.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE
|
||||
TriState.DISABLED -> Filter.TriState.STATE_IGNORE
|
||||
TriState.ENABLED_IS -> Filter.TriState.STATE_INCLUDE
|
||||
TriState.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ class GlobalMangaSearchScreen(
|
|||
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 GlobalMangaSearchScreen(
|
|||
} else {
|
||||
GlobalMangaSearchScreen(
|
||||
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.manga.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.manga.service.MangaSourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -15,8 +20,8 @@ class GlobalMangaSearchScreenModel(
|
|||
preferences: BasePreferences = Injekt.get(),
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: MangaSourceManager = Injekt.get(),
|
||||
) : MangaSearchScreenModel<GlobalMangaSearchState>(
|
||||
GlobalMangaSearchState(
|
||||
) : MangaSearchScreenModel<GlobalMangaSearchScreenModel.State>(
|
||||
State(
|
||||
searchQuery = initialQuery,
|
||||
),
|
||||
) {
|
||||
|
@ -24,6 +29,13 @@ class GlobalMangaSearchScreenModel(
|
|||
val incognitoMode = preferences.incognitoMode()
|
||||
val lastUsedSourceId = sourcePreferences.lastUsedMangaSource()
|
||||
|
||||
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()) {
|
||||
|
@ -37,6 +49,7 @@ class GlobalMangaSearchScreenModel(
|
|||
val pinnedSources = sourcePreferences.pinnedMangaSources().get()
|
||||
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { mutableState.value.sourceFilter != MangaSourceFilter.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})" }))
|
||||
|
@ -57,15 +70,29 @@ class GlobalMangaSearchScreenModel(
|
|||
override fun getItems(): Map<CatalogueSource, MangaSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class GlobalMangaSearchState(
|
||||
fun setSourceFilter(filter: MangaSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MangaSearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
||||
return !onlyShowHasResults || (this is MangaSearchItemResult.Success && !this.isEmpty)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: MangaSourceFilter = MangaSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, MangaSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is MangaSearchItemResult.Loading }
|
||||
|
||||
val total: Int = items.size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,16 +68,7 @@ abstract class MangaSearchScreenModel<T>(
|
|||
val enabledSources = getEnabledSources()
|
||||
|
||||
if (filter.isEmpty()) {
|
||||
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedMangaSourcesOnly().get()
|
||||
val pinnedSources = sourcePreferences.pinnedMangaSources().get()
|
||||
|
||||
return enabledSources.filter {
|
||||
if (shouldSearchPinnedOnly) {
|
||||
"${it.id}" in pinnedSources
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
return enabledSources
|
||||
}
|
||||
|
||||
return extensionManager.installedExtensionsFlow.value
|
||||
|
@ -136,6 +127,11 @@ abstract class MangaSearchScreenModel<T>(
|
|||
}
|
||||
}
|
||||
|
||||
enum class MangaSourceFilter {
|
||||
All,
|
||||
PinnedOnly,
|
||||
}
|
||||
|
||||
sealed class MangaSearchItemResult {
|
||||
object Loading : MangaSearchItemResult()
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ import kotlinx.coroutines.isActive
|
|||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
|
@ -60,7 +61,6 @@ import tachiyomi.domain.category.anime.interactor.GetAnimeCategories
|
|||
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
|
||||
import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
|
||||
|
@ -161,7 +161,8 @@ class AnimeInfoScreenModel(
|
|||
combine(
|
||||
getAnimeAndEpisodes.subscribe(animeId).distinctUntilChanged(),
|
||||
downloadCache.changes,
|
||||
) { animeAndEpisodes, _ -> animeAndEpisodes }
|
||||
downloadManager.queueState,
|
||||
) { animeAndEpisodes, _, _ -> animeAndEpisodes }
|
||||
.collectLatest { (anime, episodes) ->
|
||||
updateSuccessState {
|
||||
it.copy(
|
||||
|
@ -766,13 +767,13 @@ class AnimeInfoScreenModel(
|
|||
* Sets the seen filter and requests an UI update.
|
||||
* @param state whether to display only unseen episodes or all episodes.
|
||||
*/
|
||||
fun setUnseenFilter(state: TriStateFilter) {
|
||||
fun setUnseenFilter(state: TriState) {
|
||||
val anime = successState?.anime ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Anime.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Anime.EPISODE_SHOW_UNSEEN
|
||||
TriStateFilter.ENABLED_NOT -> Anime.EPISODE_SHOW_SEEN
|
||||
TriState.DISABLED -> Anime.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Anime.EPISODE_SHOW_UNSEEN
|
||||
TriState.ENABLED_NOT -> Anime.EPISODE_SHOW_SEEN
|
||||
}
|
||||
coroutineScope.launchNonCancellable {
|
||||
setAnimeEpisodeFlags.awaitSetUnseenFilter(anime, flag)
|
||||
|
@ -783,13 +784,13 @@ class AnimeInfoScreenModel(
|
|||
* Sets the download filter and requests an UI update.
|
||||
* @param state whether to display only downloaded episodes or all episodes.
|
||||
*/
|
||||
fun setDownloadedFilter(state: TriStateFilter) {
|
||||
fun setDownloadedFilter(state: TriState) {
|
||||
val anime = successState?.anime ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Anime.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Anime.EPISODE_SHOW_DOWNLOADED
|
||||
TriStateFilter.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_DOWNLOADED
|
||||
TriState.DISABLED -> Anime.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Anime.EPISODE_SHOW_DOWNLOADED
|
||||
TriState.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_DOWNLOADED
|
||||
}
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
|
@ -801,13 +802,13 @@ class AnimeInfoScreenModel(
|
|||
* Sets the bookmark filter and requests an UI update.
|
||||
* @param state whether to display only bookmarked episodes or all episodes.
|
||||
*/
|
||||
fun setBookmarkedFilter(state: TriStateFilter) {
|
||||
fun setBookmarkedFilter(state: TriState) {
|
||||
val anime = successState?.anime ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Anime.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Anime.EPISODE_SHOW_BOOKMARKED
|
||||
TriStateFilter.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_BOOKMARKED
|
||||
TriState.DISABLED -> Anime.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Anime.EPISODE_SHOW_BOOKMARKED
|
||||
TriState.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_BOOKMARKED
|
||||
}
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
|
|
|
@ -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
|
||||
|
@ -269,6 +270,7 @@ data class AnimeTrackInfoDialogHomeScreen(
|
|||
.filter { (it.service as? EnhancedAnimeTrackService)?.accept(source) ?: true }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val trackItems: List<AnimeTrackItem> = emptyList(),
|
||||
)
|
||||
|
@ -318,6 +320,7 @@ private data class TrackStatusSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: Int,
|
||||
)
|
||||
|
@ -374,6 +377,7 @@ private data class TrackEpisodeSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: Int,
|
||||
)
|
||||
|
@ -424,6 +428,7 @@ private data class TrackScoreSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: String,
|
||||
)
|
||||
|
@ -731,6 +736,7 @@ data class TrackServiceSearchScreen(
|
|||
mutableState.update { it.copy(selected = selected) }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val queryResult: Result<List<AnimeTrackSearch>>? = null,
|
||||
val selected: AnimeTrackSearch? = null,
|
||||
|
|
|
@ -47,6 +47,7 @@ import kotlinx.coroutines.isActive
|
|||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
|
@ -57,7 +58,6 @@ import tachiyomi.domain.category.manga.interactor.GetMangaCategories
|
|||
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.entries.applyFilter
|
||||
import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters
|
||||
|
@ -156,7 +156,8 @@ class MangaInfoScreenModel(
|
|||
combine(
|
||||
getMangaAndChapters.subscribe(mangaId).distinctUntilChanged(),
|
||||
downloadCache.changes,
|
||||
) { mangaAndChapters, _ -> mangaAndChapters }
|
||||
downloadManager.queueState,
|
||||
) { mangaAndChapters, _, _ -> mangaAndChapters }
|
||||
.collectLatest { (manga, chapters) ->
|
||||
updateSuccessState {
|
||||
it.copy(
|
||||
|
@ -756,13 +757,13 @@ class MangaInfoScreenModel(
|
|||
* Sets the read filter and requests an UI update.
|
||||
* @param state whether to display only unread chapters or all chapters.
|
||||
*/
|
||||
fun setUnreadFilter(state: TriStateFilter) {
|
||||
fun setUnreadFilter(state: TriState) {
|
||||
val manga = successState?.manga ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Manga.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD
|
||||
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ
|
||||
TriState.DISABLED -> Manga.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD
|
||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ
|
||||
}
|
||||
coroutineScope.launchNonCancellable {
|
||||
setMangaChapterFlags.awaitSetUnreadFilter(manga, flag)
|
||||
|
@ -773,13 +774,13 @@ class MangaInfoScreenModel(
|
|||
* Sets the download filter and requests an UI update.
|
||||
* @param state whether to display only downloaded chapters or all chapters.
|
||||
*/
|
||||
fun setDownloadedFilter(state: TriStateFilter) {
|
||||
fun setDownloadedFilter(state: TriState) {
|
||||
val manga = successState?.manga ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Manga.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED
|
||||
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
|
||||
TriState.DISABLED -> Manga.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED
|
||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
|
||||
}
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
|
@ -791,13 +792,13 @@ class MangaInfoScreenModel(
|
|||
* Sets the bookmark filter and requests an UI update.
|
||||
* @param state whether to display only bookmarked chapters or all chapters.
|
||||
*/
|
||||
fun setBookmarkedFilter(state: TriStateFilter) {
|
||||
fun setBookmarkedFilter(state: TriState) {
|
||||
val manga = successState?.manga ?: return
|
||||
|
||||
val flag = when (state) {
|
||||
TriStateFilter.DISABLED -> Manga.SHOW_ALL
|
||||
TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED
|
||||
TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
|
||||
TriState.DISABLED -> Manga.SHOW_ALL
|
||||
TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED
|
||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
|
||||
}
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
|
|
|
@ -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
|
||||
|
@ -268,6 +269,7 @@ data class MangaTrackInfoDialogHomeScreen(
|
|||
.filter { (it.service as? EnhancedMangaTrackService)?.accept(source) ?: true }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val trackItems: List<MangaTrackItem> = emptyList(),
|
||||
)
|
||||
|
@ -317,6 +319,7 @@ private data class TrackStatusSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: Int,
|
||||
)
|
||||
|
@ -373,6 +376,7 @@ private data class TrackChapterSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: Int,
|
||||
)
|
||||
|
@ -423,6 +427,7 @@ private data class TrackScoreSelectorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: String,
|
||||
)
|
||||
|
@ -730,6 +735,7 @@ data class TrackServiceSearchScreen(
|
|||
mutableState.update { it.copy(selected = selected) }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val queryResult: Result<List<MangaTrackSearch>>? = null,
|
||||
val selected: MangaTrackSearch? = null,
|
||||
|
|
|
@ -41,13 +41,13 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.category.anime.interactor.GetVisibleAnimeCategories
|
||||
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||
|
@ -154,7 +154,7 @@ class AnimeLibraryScreenModel(
|
|||
prefs.filterBookmarked,
|
||||
prefs.filterCompleted,
|
||||
) + trackFilter.values
|
||||
).any { it != TriStateFilter.DISABLED }
|
||||
).any { it != TriState.DISABLED }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
|
@ -170,12 +170,12 @@ class AnimeLibraryScreenModel(
|
|||
*/
|
||||
private suspend fun AnimeLibraryMap.applyFilters(
|
||||
trackMap: Map<Long, List<Long>>,
|
||||
loggedInTrackServices: Map<Long, TriStateFilter>,
|
||||
loggedInTrackServices: Map<Long, TriState>,
|
||||
): AnimeLibraryMap {
|
||||
val prefs = getAnimelibItemPreferencesFlow().first()
|
||||
val downloadedOnly = prefs.globalFilterDownloaded
|
||||
val filterDownloaded =
|
||||
if (downloadedOnly) TriStateFilter.ENABLED_IS else prefs.filterDownloaded
|
||||
if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded
|
||||
val filterUnseen = prefs.filterUnseen
|
||||
val filterStarted = prefs.filterStarted
|
||||
val filterBookmarked = prefs.filterBookmarked
|
||||
|
@ -183,8 +183,8 @@ class AnimeLibraryScreenModel(
|
|||
|
||||
val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
|
||||
|
||||
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_IS) it.key else null }
|
||||
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
|
||||
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
|
||||
|
||||
val filterFnDownloaded: (AnimeLibraryItem) -> Boolean = {
|
||||
|
@ -315,11 +315,11 @@ class AnimeLibraryScreenModel(
|
|||
localBadge = it[1] as Boolean,
|
||||
languageBadge = it[2] as Boolean,
|
||||
globalFilterDownloaded = it[3] as Boolean,
|
||||
filterDownloaded = it[4] as TriStateFilter,
|
||||
filterUnseen = it[5] as TriStateFilter,
|
||||
filterStarted = it[6] as TriStateFilter,
|
||||
filterBookmarked = it[7] as TriStateFilter,
|
||||
filterCompleted = it[8] as TriStateFilter,
|
||||
filterDownloaded = it[4] as TriState,
|
||||
filterUnseen = it[5] as TriState,
|
||||
filterStarted = it[6] as TriState,
|
||||
filterBookmarked = it[7] as TriState,
|
||||
filterCompleted = it[8] as TriState,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -372,7 +372,7 @@ class AnimeLibraryScreenModel(
|
|||
*
|
||||
* @return map of track id with the filter value
|
||||
*/
|
||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriStateFilter>> {
|
||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
|
||||
val loggedServices = trackManager.services.filter { it.isLogged && it is AnimeTrackService }
|
||||
return if (loggedServices.isNotEmpty()) {
|
||||
val prefFlows = loggedServices
|
||||
|
@ -677,11 +677,11 @@ class AnimeLibraryScreenModel(
|
|||
val languageBadge: Boolean,
|
||||
|
||||
val globalFilterDownloaded: Boolean,
|
||||
val filterDownloaded: TriStateFilter,
|
||||
val filterUnseen: TriStateFilter,
|
||||
val filterStarted: TriStateFilter,
|
||||
val filterBookmarked: TriStateFilter,
|
||||
val filterCompleted: TriStateFilter,
|
||||
val filterDownloaded: TriState,
|
||||
val filterUnseen: TriState,
|
||||
val filterStarted: TriState,
|
||||
val filterBookmarked: TriState,
|
||||
val filterCompleted: TriState,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -6,12 +6,12 @@ import eu.kanade.domain.base.BasePreferences
|
|||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.util.preference.toggle
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.getAndSet
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.domain.category.anime.interactor.SetAnimeDisplayMode
|
||||
import tachiyomi.domain.category.anime.interactor.SetSortModeForAnimeCategory
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.library.anime.model.AnimeLibrarySort
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
|
@ -33,7 +33,7 @@ class AnimeLibrarySettingsScreenModel(
|
|||
preference(libraryPreferences).toggle()
|
||||
}
|
||||
|
||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriStateFilter>) {
|
||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
|
||||
preference(libraryPreferences).getAndSet {
|
||||
it.next()
|
||||
}
|
||||
|
|
|
@ -41,13 +41,13 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.category.manga.interactor.GetVisibleMangaCategories
|
||||
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.entries.applyFilter
|
||||
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
|
@ -154,7 +154,7 @@ class MangaLibraryScreenModel(
|
|||
prefs.filterBookmarked,
|
||||
prefs.filterCompleted,
|
||||
) + trackFilter.values
|
||||
).any { it != TriStateFilter.DISABLED }
|
||||
).any { it != TriState.DISABLED }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
|
@ -170,12 +170,12 @@ class MangaLibraryScreenModel(
|
|||
*/
|
||||
private suspend fun MangaLibraryMap.applyFilters(
|
||||
trackMap: Map<Long, List<Long>>,
|
||||
loggedInTrackServices: Map<Long, TriStateFilter>,
|
||||
loggedInTrackServices: Map<Long, TriState>,
|
||||
): MangaLibraryMap {
|
||||
val prefs = getLibraryItemPreferencesFlow().first()
|
||||
val downloadedOnly = prefs.globalFilterDownloaded
|
||||
val filterDownloaded =
|
||||
if (downloadedOnly) TriStateFilter.ENABLED_IS else prefs.filterDownloaded
|
||||
if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded
|
||||
val filterUnread = prefs.filterUnread
|
||||
val filterStarted = prefs.filterStarted
|
||||
val filterBookmarked = prefs.filterBookmarked
|
||||
|
@ -183,8 +183,8 @@ class MangaLibraryScreenModel(
|
|||
|
||||
val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
|
||||
|
||||
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_IS) it.key else null }
|
||||
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
|
||||
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
|
||||
|
||||
val filterFnDownloaded: (MangaLibraryItem) -> Boolean = {
|
||||
|
@ -309,11 +309,11 @@ class MangaLibraryScreenModel(
|
|||
localBadge = it[1] as Boolean,
|
||||
languageBadge = it[2] as Boolean,
|
||||
globalFilterDownloaded = it[3] as Boolean,
|
||||
filterDownloaded = it[4] as TriStateFilter,
|
||||
filterUnread = it[5] as TriStateFilter,
|
||||
filterStarted = it[6] as TriStateFilter,
|
||||
filterBookmarked = it[7] as TriStateFilter,
|
||||
filterCompleted = it[8] as TriStateFilter,
|
||||
filterDownloaded = it[4] as TriState,
|
||||
filterUnread = it[5] as TriState,
|
||||
filterStarted = it[6] as TriState,
|
||||
filterBookmarked = it[7] as TriState,
|
||||
filterCompleted = it[8] as TriState,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -366,7 +366,7 @@ class MangaLibraryScreenModel(
|
|||
*
|
||||
* @return map of track id with the filter value
|
||||
*/
|
||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriStateFilter>> {
|
||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
|
||||
val loggedServices = trackManager.services.filter { it.isLogged && it is MangaTrackService }
|
||||
return if (loggedServices.isNotEmpty()) {
|
||||
val prefFlows = loggedServices
|
||||
|
@ -671,11 +671,11 @@ class MangaLibraryScreenModel(
|
|||
val languageBadge: Boolean,
|
||||
|
||||
val globalFilterDownloaded: Boolean,
|
||||
val filterDownloaded: TriStateFilter,
|
||||
val filterUnread: TriStateFilter,
|
||||
val filterStarted: TriStateFilter,
|
||||
val filterBookmarked: TriStateFilter,
|
||||
val filterCompleted: TriStateFilter,
|
||||
val filterDownloaded: TriState,
|
||||
val filterUnread: TriState,
|
||||
val filterStarted: TriState,
|
||||
val filterBookmarked: TriState,
|
||||
val filterCompleted: TriState,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -6,12 +6,12 @@ import eu.kanade.domain.base.BasePreferences
|
|||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.util.preference.toggle
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.getAndSet
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.domain.category.manga.interactor.SetMangaDisplayMode
|
||||
import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.library.manga.model.MangaLibrarySort
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
|
@ -33,7 +33,7 @@ class MangaLibrarySettingsScreenModel(
|
|||
preference(libraryPreferences).toggle()
|
||||
}
|
||||
|
||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriStateFilter>) {
|
||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
|
||||
preference(libraryPreferences).getAndSet {
|
||||
it.next()
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.player
|
|||
|
||||
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
|
||||
|
@ -719,6 +720,7 @@ class PlayerViewModel(
|
|||
mutableState.update { it.copy(dialog = null, sheet = null) }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val episodeList: List<Episode> = emptyList(),
|
||||
val episode: Episode? = null,
|
||||
|
|
|
@ -15,11 +15,11 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.OutlinedNumericChooser
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerSettingsScreenModel
|
||||
import `is`.xyz.mpv.MPVLib
|
||||
import tachiyomi.presentation.core.components.OutlinedNumericChooser
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -32,12 +32,12 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.yubyf.truetypeparser.TTFFile
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.components.OutlinedNumericChooser
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerSettingsScreenModel
|
||||
import `is`.xyz.mpv.MPVLib
|
||||
import tachiyomi.presentation.core.components.OutlinedNumericChooser
|
||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import java.io.File
|
||||
|
|
|
@ -31,6 +31,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.ColorUtils
|
||||
|
@ -50,6 +51,7 @@ import eu.kanade.domain.base.BasePreferences
|
|||
import eu.kanade.domain.entries.manga.model.orientationType
|
||||
import eu.kanade.presentation.reader.ChapterNavigator
|
||||
import eu.kanade.presentation.reader.PageIndicatorText
|
||||
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.core.Constants
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
|
@ -65,8 +67,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
|||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderColorFilterDialog
|
||||
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
|
||||
|
@ -392,6 +394,8 @@ class ReaderActivity : BaseActivity() {
|
|||
|
||||
binding.dialogRoot.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val settingsScreenModel = remember { ReaderSettingsScreenModel() }
|
||||
|
||||
val onDismissRequest = viewModel::closeDialog
|
||||
when (state.dialog) {
|
||||
is ReaderViewModel.Dialog.Loading -> {
|
||||
|
@ -409,14 +413,12 @@ class ReaderActivity : BaseActivity() {
|
|||
},
|
||||
)
|
||||
}
|
||||
is ReaderViewModel.Dialog.ColorFilter -> {
|
||||
setMenuVisibility(false)
|
||||
ReaderColorFilterDialog(
|
||||
onDismissRequest = {
|
||||
onDismissRequest()
|
||||
setMenuVisibility(true)
|
||||
},
|
||||
readerPreferences = viewModel.readerPreferences,
|
||||
is ReaderViewModel.Dialog.Settings -> {
|
||||
ReaderSettingsDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onShowMenus = { setMenuVisibility(true) },
|
||||
onHideMenus = { setMenuVisibility(false) },
|
||||
screenModel = settingsScreenModel,
|
||||
)
|
||||
}
|
||||
is ReaderViewModel.Dialog.PageActions -> {
|
||||
|
@ -549,7 +551,7 @@ class ReaderActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
// Settings sheet
|
||||
with(binding.actionSettings) {
|
||||
with(binding.actionSettingsLegacy) {
|
||||
setTooltip(R.string.action_settings)
|
||||
|
||||
var readerSettingSheet: ReaderSettingsSheet? = null
|
||||
|
@ -559,13 +561,11 @@ class ReaderActivity : BaseActivity() {
|
|||
readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() }
|
||||
}
|
||||
}
|
||||
|
||||
// Color filter sheet
|
||||
with(binding.actionColorSettings) {
|
||||
setTooltip(R.string.custom_filter)
|
||||
with(binding.actionSettings) {
|
||||
setTooltip(R.string.action_settings)
|
||||
|
||||
setOnClickListener {
|
||||
viewModel.openColorFilterDialog()
|
||||
viewModel.openSettingsDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -704,8 +705,8 @@ class ReaderViewModel(
|
|||
mutableState.update { it.copy(dialog = Dialog.PageActions(page)) }
|
||||
}
|
||||
|
||||
fun openColorFilterDialog() {
|
||||
mutableState.update { it.copy(dialog = Dialog.ColorFilter) }
|
||||
fun openSettingsDialog() {
|
||||
mutableState.update { it.copy(dialog = Dialog.Settings) }
|
||||
}
|
||||
|
||||
fun closeDialog() {
|
||||
|
@ -809,16 +810,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()
|
||||
|
@ -893,6 +890,7 @@ class ReaderViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val viewerChapters: ViewerChapters? = null,
|
||||
|
@ -912,7 +910,7 @@ class ReaderViewModel(
|
|||
|
||||
sealed class Dialog {
|
||||
object Loading : Dialog()
|
||||
object ColorFilter : Dialog()
|
||||
object Settings : Dialog()
|
||||
data class PageActions(val page: ReaderPage) : Dialog()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.PreferenceScreen
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.core.preference.getAndSet
|
||||
|
||||
@Composable
|
||||
fun ReaderColorFilterDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
readerPreferences: ReaderPreferences,
|
||||
) {
|
||||
val colorFilterModes = buildList {
|
||||
addAll(
|
||||
listOf(
|
||||
R.string.label_default,
|
||||
R.string.filter_mode_multiply,
|
||||
R.string.filter_mode_screen,
|
||||
),
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
addAll(
|
||||
listOf(
|
||||
R.string.filter_mode_overlay,
|
||||
R.string.filter_mode_lighten,
|
||||
R.string.filter_mode_darken,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.map { stringResource(it) }
|
||||
|
||||
val customBrightness by readerPreferences.customBrightness().collectAsState()
|
||||
val customBrightnessValue by readerPreferences.customBrightnessValue().collectAsState()
|
||||
val colorFilter by readerPreferences.colorFilter().collectAsState()
|
||||
val colorFilterValue by readerPreferences.colorFilterValue().collectAsState()
|
||||
val colorFilterMode by readerPreferences.colorFilterMode().collectAsState()
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
(LocalView.current.parent as? DialogWindowProvider)?.window?.setDimAmount(0f)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalPreferenceMinHeight provides 48.dp,
|
||||
) {
|
||||
PreferenceScreen(
|
||||
items = listOfNotNull(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.customBrightness(),
|
||||
title = stringResource(R.string.pref_custom_brightness),
|
||||
),
|
||||
/**
|
||||
* Sets the brightness of the screen. Range is [-75, 100].
|
||||
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
||||
* From 1 to 100 it sets that value as brightness.
|
||||
* 0 sets system brightness and hides the overlay.
|
||||
*/
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = customBrightnessValue,
|
||||
title = stringResource(R.string.pref_custom_brightness),
|
||||
min = -75,
|
||||
max = 100,
|
||||
onValueChanged = {
|
||||
readerPreferences.customBrightnessValue().set(it)
|
||||
true
|
||||
},
|
||||
).takeIf { customBrightness },
|
||||
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.colorFilter(),
|
||||
title = stringResource(R.string.pref_custom_color_filter),
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = colorFilterValue.red,
|
||||
title = stringResource(R.string.color_filter_r_value),
|
||||
max = 255,
|
||||
onValueChanged = { newRValue ->
|
||||
readerPreferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newRValue, RED_MASK, 16)
|
||||
}
|
||||
true
|
||||
},
|
||||
).takeIf { colorFilter },
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = colorFilterValue.green,
|
||||
title = stringResource(R.string.color_filter_g_value),
|
||||
max = 255,
|
||||
onValueChanged = { newRValue ->
|
||||
readerPreferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newRValue, GREEN_MASK, 8)
|
||||
}
|
||||
true
|
||||
},
|
||||
).takeIf { colorFilter },
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = colorFilterValue.blue,
|
||||
title = stringResource(R.string.color_filter_b_value),
|
||||
max = 255,
|
||||
onValueChanged = { newRValue ->
|
||||
readerPreferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newRValue, BLUE_MASK, 0)
|
||||
}
|
||||
true
|
||||
},
|
||||
).takeIf { colorFilter },
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = colorFilterValue.alpha,
|
||||
title = stringResource(R.string.color_filter_a_value),
|
||||
max = 255,
|
||||
onValueChanged = { newRValue ->
|
||||
readerPreferences.colorFilterValue().getAndSet {
|
||||
getColorValue(it, newRValue, ALPHA_MASK, 24)
|
||||
}
|
||||
true
|
||||
},
|
||||
).takeIf { colorFilter },
|
||||
Preference.PreferenceItem.BasicListPreference(
|
||||
value = colorFilterMode.toString(),
|
||||
title = stringResource(R.string.pref_color_filter_mode),
|
||||
entries = colorFilterModes
|
||||
.mapIndexed { index, mode -> index.toString() to mode }
|
||||
.toMap(),
|
||||
onValueChanged = { newValue ->
|
||||
readerPreferences.colorFilterMode().set(newValue.toInt())
|
||||
true
|
||||
},
|
||||
).takeIf { colorFilter },
|
||||
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.grayscale(),
|
||||
title = stringResource(R.string.pref_grayscale),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.invertedColors(),
|
||||
title = stringResource(R.string.pref_inverted_colors),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getColorValue(currentColor: Int, color: Int, mask: Long, bitShift: Int): Int {
|
||||
return (color shl bitShift) or (currentColor and mask.inv().toInt())
|
||||
}
|
||||
private const val ALPHA_MASK: Long = 0xFF000000
|
||||
private const val RED_MASK: Long = 0x00FF0000
|
||||
private const val GREEN_MASK: Long = 0x0000FF00
|
||||
private const val BLUE_MASK: Long = 0x000000FF
|
|
@ -1,53 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.ReaderGeneralSettingsBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.preference.bindToPreference
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Sheet to show reader and viewer preferences.
|
||||
*/
|
||||
class ReaderGeneralSettings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
NestedScrollView(context, attrs) {
|
||||
|
||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||
|
||||
private val binding = ReaderGeneralSettingsBinding.inflate(LayoutInflater.from(context), this, false)
|
||||
|
||||
init {
|
||||
addView(binding.root)
|
||||
|
||||
initGeneralPreferences()
|
||||
}
|
||||
|
||||
/**
|
||||
* Init general reader preferences.
|
||||
*/
|
||||
private fun initGeneralPreferences() {
|
||||
binding.backgroundColor.bindToIntPreference(readerPreferences.readerTheme(), R.array.reader_themes_values)
|
||||
binding.showPageNumber.bindToPreference(readerPreferences.showPageNumber())
|
||||
binding.fullscreen.bindToPreference(readerPreferences.fullscreen())
|
||||
readerPreferences.fullscreen().changes()
|
||||
.onEach {
|
||||
// If the preference is explicitly disabled, that means the setting was configured since there is a cutout
|
||||
binding.cutoutShort.isVisible = it && ((context as ReaderActivity).hasCutout || !readerPreferences.cutoutShort().get())
|
||||
binding.cutoutShort.bindToPreference(readerPreferences.cutoutShort())
|
||||
}
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
|
||||
binding.keepscreen.bindToPreference(readerPreferences.keepScreenOn())
|
||||
binding.longTap.bindToPreference(readerPreferences.readWithLongTap())
|
||||
binding.alwaysShowChapterTransition.bindToPreference(readerPreferences.alwaysShowChapterTransition())
|
||||
binding.pageTransitions.bindToPreference(readerPreferences.pageTransitions())
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ class ReaderPreferences(
|
|||
|
||||
fun cropBordersWebtoon() = preferenceStore.getBoolean("crop_borders_webtoon", false)
|
||||
|
||||
fun webtoonSidePadding() = preferenceStore.getInt("webtoon_side_padding", 0)
|
||||
fun webtoonSidePadding() = preferenceStore.getInt("webtoon_side_padding", WEBTOON_PADDING_MIN)
|
||||
|
||||
fun readerHideThreshold() = preferenceStore.getEnum("reader_hide_threshold", ReaderHideThreshold.LOW)
|
||||
|
||||
|
@ -137,4 +137,9 @@ class ReaderPreferences(
|
|||
LOW(31),
|
||||
LOWEST(47),
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WEBTOON_PADDING_MIN = 0
|
||||
const val WEBTOON_PADDING_MAX = 25
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import eu.kanade.domain.entries.manga.model.orientationType
|
||||
import eu.kanade.domain.entries.manga.model.readingModeType
|
||||
import eu.kanade.tachiyomi.R
|
||||
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 eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Sheet to show reader and viewer preferences.
|
||||
*/
|
||||
class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
NestedScrollView(context, attrs) {
|
||||
|
||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||
|
||||
private val binding = ReaderReadingModeSettingsBinding.inflate(LayoutInflater.from(context), this, false)
|
||||
|
||||
init {
|
||||
addView(binding.root)
|
||||
|
||||
initGeneralPreferences()
|
||||
|
||||
when ((context as ReaderActivity).viewModel.state.value.viewer) {
|
||||
is PagerViewer -> initPagerPreferences()
|
||||
is WebtoonViewer -> initWebtoonPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init general reader preferences.
|
||||
*/
|
||||
private fun initGeneralPreferences() {
|
||||
binding.viewer.onItemSelectedListener = { position ->
|
||||
val readingModeType = ReadingModeType.fromSpinner(position)
|
||||
(context as ReaderActivity).viewModel.setMangaReadingMode(readingModeType.flagValue)
|
||||
|
||||
val mangaViewer = (context as ReaderActivity).viewModel.getMangaReadingMode()
|
||||
if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) {
|
||||
initWebtoonPreferences()
|
||||
} else {
|
||||
initPagerPreferences()
|
||||
}
|
||||
}
|
||||
binding.viewer.setSelection((context as ReaderActivity).viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
|
||||
|
||||
binding.rotationMode.onItemSelectedListener = { position ->
|
||||
val rotationType = OrientationType.fromSpinner(position)
|
||||
(context as ReaderActivity).viewModel.setMangaOrientationType(rotationType.flagValue)
|
||||
}
|
||||
binding.rotationMode.setSelection((context as ReaderActivity).viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the preferences for the pager reader.
|
||||
*/
|
||||
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.navigatePan.bindToPreference(readerPreferences.navigateToPan())
|
||||
|
||||
binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager())
|
||||
readerPreferences.navigationModePager().changes()
|
||||
.onEach {
|
||||
val isTappingEnabled = it != 5
|
||||
binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled
|
||||
binding.pagerPrefsGroup.navigatePan.isVisible = isTappingEnabled
|
||||
}
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
// Makes so that landscape zoom gets hidden away when image scale type is not fit screen
|
||||
binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1)
|
||||
readerPreferences.imageScaleType().changes()
|
||||
.onEach { binding.pagerPrefsGroup.landscapeZoom.isVisible = it == 1 }
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
binding.pagerPrefsGroup.landscapeZoom.bindToPreference(readerPreferences.landscapeZoom())
|
||||
|
||||
binding.pagerPrefsGroup.zoomStart.bindToPreference(readerPreferences.zoomStart(), 1)
|
||||
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
|
||||
|
||||
binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
|
||||
readerPreferences.dualPageSplitPaged().changes()
|
||||
.onEach {
|
||||
binding.pagerPrefsGroup.dualPageInvert.isVisible = it
|
||||
if (it) {
|
||||
binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
|
||||
}
|
||||
}
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
|
||||
|
||||
binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit())
|
||||
readerPreferences.dualPageRotateToFit().changes()
|
||||
.onEach {
|
||||
binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it
|
||||
if (it) {
|
||||
binding.pagerPrefsGroup.dualPageSplit.isChecked = false
|
||||
}
|
||||
}
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert())
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the preferences for the webtoon reader.
|
||||
*/
|
||||
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((context as ReaderActivity).lifecycleScope)
|
||||
binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(readerPreferences.cropBordersWebtoon())
|
||||
binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(readerPreferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
|
||||
|
||||
binding.webtoonPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitWebtoon())
|
||||
// Makes it so that dual page invert gets hidden away when dual page split is turned off
|
||||
readerPreferences.dualPageSplitWebtoon().changes()
|
||||
.onEach { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it }
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertWebtoon())
|
||||
binding.webtoonPrefsGroup.longStripSplit.isVisible = !isReleaseBuildType
|
||||
binding.webtoonPrefsGroup.longStripSplit.bindToPreference(readerPreferences.longStripSplitWebtoon())
|
||||
|
||||
binding.webtoonPrefsGroup.doubleTapZoom.bindToPreference(readerPreferences.webtoonDoubleTapZoomEnabled())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import eu.kanade.tachiyomi.util.preference.toggle
|
||||
import tachiyomi.core.preference.Preference
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ReaderSettingsScreenModel(
|
||||
val preferences: ReaderPreferences = Injekt.get(),
|
||||
) : ScreenModel {
|
||||
|
||||
fun togglePreference(preference: (ReaderPreferences) -> Preference<Boolean>) {
|
||||
preference(preferences).toggle()
|
||||
}
|
||||
}
|
|
@ -1,55 +1,89 @@
|
|||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import eu.kanade.domain.entries.manga.model.orientationType
|
||||
import eu.kanade.domain.entries.manga.model.readingModeType
|
||||
import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class ReaderSettingsSheet(
|
||||
private val activity: ReaderActivity,
|
||||
) : BaseBottomSheetDialog(activity) {
|
||||
) : BottomSheetDialog(activity) {
|
||||
|
||||
private val tabs = listOf(
|
||||
ReaderReadingModeSettings(activity) to R.string.pref_category_reading_mode,
|
||||
ReaderGeneralSettings(activity) to R.string.pref_category_general,
|
||||
)
|
||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||
|
||||
private lateinit var binding: CommonTabbedSheetBinding
|
||||
|
||||
override fun createView(inflater: LayoutInflater): View {
|
||||
binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
|
||||
|
||||
val adapter = Adapter()
|
||||
binding.pager.adapter = adapter
|
||||
binding.tabs.setupWithViewPager(binding.pager)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
private lateinit var binding: ReaderReadingModeSettingsBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
behavior.isFitToContents = false
|
||||
behavior.halfExpandedRatio = 0.25f
|
||||
binding = ReaderReadingModeSettingsBinding.inflate(activity.layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
initGeneralPreferences()
|
||||
|
||||
when (activity.viewModel.state.value.viewer) {
|
||||
is PagerViewer -> initPagerPreferences()
|
||||
is WebtoonViewer -> initWebtoonPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Adapter : ViewPagerAdapter() {
|
||||
private fun initGeneralPreferences() {
|
||||
binding.viewer.onItemSelectedListener = { position ->
|
||||
val readingModeType = ReadingModeType.fromSpinner(position)
|
||||
activity.viewModel.setMangaReadingMode(readingModeType.flagValue)
|
||||
|
||||
override fun createView(container: ViewGroup, position: Int): View {
|
||||
return tabs[position].first
|
||||
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)
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return tabs.size
|
||||
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)
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return activity.resources!!.getString(tabs[position].second)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.view.Gravity
|
|||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.ArrayRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
|
@ -99,17 +98,6 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
|
|||
}
|
||||
}
|
||||
|
||||
fun bindToIntPreference(pref: Preference<Int>, @ArrayRes intValuesResource: Int, block: ((Int) -> Unit)? = null) {
|
||||
val intValues = resources.getStringArray(intValuesResource).map { it.toIntOrNull() }
|
||||
setSelection(intValues.indexOf(pref.get()))
|
||||
|
||||
popup = makeSettingsPopup(pref, intValues, block)
|
||||
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)
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package eu.kanade.tachiyomi.widget.sheet
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.getElevation
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.displayCompat
|
||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||
|
||||
abstract class BaseBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
abstract fun createView(inflater: LayoutInflater): View
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val rootView = createView(layoutInflater)
|
||||
setContentView(rootView)
|
||||
|
||||
// Enforce max width for tablets
|
||||
val width = context.resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
|
||||
if (width > 0) {
|
||||
behavior.maxWidth = width
|
||||
}
|
||||
|
||||
// Set peek height to 50% display height
|
||||
context.displayCompat?.let {
|
||||
val metrics = DisplayMetrics()
|
||||
it.getRealMetrics(metrics)
|
||||
behavior.peekHeight = metrics.heightPixels / 2
|
||||
}
|
||||
|
||||
// Set navbar color to transparent for edge-to-edge bottom sheet if we can use light navigation bar
|
||||
// TODO Replace deprecated systemUiVisibility when material-components uses new API to modify status bar icons
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
window?.setNavigationBarTransparentCompat(context, behavior.getElevation())
|
||||
val bottomSheet = rootView.parent as ViewGroup
|
||||
var flags = bottomSheet.systemUiVisibility
|
||||
flags = if (context.isNightMode()) {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
} else {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
}
|
||||
bottomSheet.systemUiVisibility = flags
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package eu.kanade.tachiyomi.widget.sheet
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import java.lang.reflect.Field
|
||||
|
||||
/**
|
||||
* From https://github.com/kafumi/android-bottomsheet-viewpager
|
||||
*/
|
||||
class BottomSheetViewPager @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
) : ViewPager(context, attrs) {
|
||||
|
||||
private val positionField: Field = LayoutParams::class.java.getDeclaredField("position").also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
|
||||
override fun getChildAt(index: Int): View {
|
||||
val currentView = getCurrentView() ?: return super.getChildAt(index)
|
||||
return if (index == 0) {
|
||||
currentView
|
||||
} else {
|
||||
var view = super.getChildAt(index)
|
||||
if (view == currentView) {
|
||||
view = super.getChildAt(0)
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentView(): View? {
|
||||
for (i in 0 until childCount) {
|
||||
val child = super.getChildAt(i)
|
||||
val lp = child.layoutParams as? LayoutParams
|
||||
if (lp != null) {
|
||||
val position = positionField.getInt(lp)
|
||||
if (!lp.isDecor && currentItem == position) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
init {
|
||||
addOnPageChangeListener(
|
||||
object : SimpleOnPageChangeListener() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
requestLayout()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z" />
|
||||
</vector>
|
|
@ -1,51 +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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/transparent_tabs_background">
|
||||
|
||||
<!-- Remove background color so rounded sheet corners work -->
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/menu"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tabMaxWidth="0dp"
|
||||
app:tabGravity="fill"
|
||||
app:tabMode="fixed" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/menu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_menu"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_overflow_24dp"
|
||||
app:tint="?attr/colorOnSurface"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<eu.kanade.tachiyomi.widget.sheet.BottomSheetViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
|
@ -120,29 +120,29 @@
|
|||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_settings"
|
||||
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_color_settings"
|
||||
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_color_settings"
|
||||
android:id="@+id/action_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/custom_filter"
|
||||
android:contentDescription="@string/action_settings"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/action_settings"
|
||||
app:layout_constraintStart_toEndOf="@id/action_settings_legacy"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_brightness_5_24dp"
|
||||
app:srcCompat="@drawable/ic_settings_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -1,87 +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">
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/background_color"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/reader_themes"
|
||||
app:title="@string/pref_reader_theme" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/show_page_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_show_page_number"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/fullscreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_fullscreen"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/cutout_short"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_cutout_short"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/keepscreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_keep_screen_on"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/long_tap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_read_with_long_tap"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/always_show_chapter_transition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_always_show_chapter_transition"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/page_transitions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_page_transitions"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,7 +1,6 @@
|
|||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
@ -45,77 +44,4 @@
|
|||
android:entries="@array/zoom_start"
|
||||
app:title="@string/pref_zoom_start" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/crop_borders"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_crop_borders"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/landscape_zoom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_landscape_zoom"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/navigate_pan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_navigate_pan"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_split"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_dual_page_split"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_invert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_dual_page_invert"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_rotate_to_fit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_page_rotate"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_rotate_to_fit_invert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_page_rotate_invert"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/tapping_prefs_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert,dual_page_rotate_to_fit,dual_page_rotate_to_fit_invert" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
android:entries="@array/rotation_type"
|
||||
app:title="@string/rotation_type" />
|
||||
|
||||
<!-- Pager preferences -->
|
||||
<include
|
||||
android:id="@+id/pager_prefs_group"
|
||||
layout="@layout/reader_pager_settings"
|
||||
|
@ -45,7 +44,6 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Webtoon preferences -->
|
||||
<include
|
||||
android:id="@+id/webtoon_prefs_group"
|
||||
layout="@layout/reader_webtoon_settings"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
@ -30,64 +29,4 @@
|
|||
android:entries="@array/invert_tapping_mode"
|
||||
app:title="@string/pref_read_with_tapping_inverted" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/webtoon_side_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/webtoon_side_padding"
|
||||
app:title="@string/pref_webtoon_side_padding" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/crop_borders_webtoon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_crop_borders"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_split"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_dual_page_split"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/dual_page_invert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/pref_dual_page_invert"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/long_strip_split"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/pref_long_strip_split"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/double_tap_zoom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/pref_double_tap_zoom"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/tapping_prefs_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="webtoon_nav,tapping_inverted,dual_page_split,dual_page_invert" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<resources>
|
||||
<dimen name="bottom_sheet_width">480dp</dimen>
|
||||
|
||||
<dimen name="screen_edge_margin">24dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -9,20 +9,6 @@
|
|||
<item>@string/vertical_plus_viewer</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reader_themes">
|
||||
<item>@string/black_background</item>
|
||||
<item>@string/gray_background</item>
|
||||
<item>@string/white_background</item>
|
||||
<item>@string/automatic_background</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reader_themes_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>0</item>
|
||||
<item>3</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="image_scale_type">
|
||||
<item>@string/scale_type_fit_screen</item>
|
||||
<item>@string/scale_type_stretch</item>
|
||||
|
@ -32,24 +18,6 @@
|
|||
<item>@string/scale_type_smart_fit</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="webtoon_side_padding">
|
||||
<item>@string/webtoon_side_padding_0</item>
|
||||
<item>@string/webtoon_side_padding_5</item>
|
||||
<item>@string/webtoon_side_padding_10</item>
|
||||
<item>@string/webtoon_side_padding_15</item>
|
||||
<item>@string/webtoon_side_padding_20</item>
|
||||
<item>@string/webtoon_side_padding_25</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="webtoon_side_padding_values">
|
||||
<item>0</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>15</item>
|
||||
<item>20</item>
|
||||
<item>25</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="zoom_start">
|
||||
<item>@string/zoom_start_automatic</item>
|
||||
<item>@string/zoom_start_left</item>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<resources>
|
||||
<dimen name="bottom_sheet_width">0dp</dimen>
|
||||
|
||||
<dimen name="dialog_radius">8dp</dimen>
|
||||
|
||||
<dimen name="screen_edge_margin">16dp</dimen>
|
||||
|
|
|
@ -44,6 +44,8 @@ class NetworkHelper(
|
|||
builder.addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}
|
||||
|
||||
builder.addInterceptor(cloudflareInterceptor)
|
||||
|
||||
when (preferences.dohProvider().get()) {
|
||||
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
||||
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
||||
|
@ -64,12 +66,12 @@ class NetworkHelper(
|
|||
|
||||
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 = client
|
||||
|
||||
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
|
||||
}
|
||||
|
|
16
core/src/main/java/tachiyomi/core/preference/TriState.kt
Normal file
16
core/src/main/java/tachiyomi/core/preference/TriState.kt
Normal file
|
@ -0,0 +1,16 @@
|
|||
package tachiyomi.core.preference
|
||||
|
||||
enum class TriState {
|
||||
DISABLED, // Disable filter
|
||||
ENABLED_IS, // Enabled with "is" filter
|
||||
ENABLED_NOT, // Enabled with "not" filter
|
||||
;
|
||||
|
||||
fun next(): TriState {
|
||||
return when (this) {
|
||||
DISABLED -> ENABLED_IS
|
||||
ENABLED_IS -> ENABLED_NOT
|
||||
ENABLED_NOT -> DISABLED
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package tachiyomi.domain.entries
|
||||
|
||||
import tachiyomi.core.preference.TriState
|
||||
|
||||
inline fun applyFilter(filter: TriState, predicate: () -> Boolean): Boolean = when (filter) {
|
||||
TriState.DISABLED -> true
|
||||
TriState.ENABLED_IS -> predicate()
|
||||
TriState.ENABLED_NOT -> !predicate()
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package tachiyomi.domain.entries
|
||||
|
||||
enum class TriStateFilter {
|
||||
DISABLED, // Disable filter
|
||||
ENABLED_IS, // Enabled with "is" filter
|
||||
ENABLED_NOT, // Enabled with "not" filter
|
||||
;
|
||||
|
||||
fun next(): TriStateFilter {
|
||||
return when (this) {
|
||||
DISABLED -> ENABLED_IS
|
||||
ENABLED_IS -> ENABLED_NOT
|
||||
ENABLED_NOT -> DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun applyFilter(filter: TriStateFilter, predicate: () -> Boolean): Boolean = when (filter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> predicate()
|
||||
TriStateFilter.ENABLED_NOT -> !predicate()
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package tachiyomi.domain.entries.anime.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import java.io.Serializable
|
||||
import kotlin.math.pow
|
||||
|
||||
|
@ -54,18 +54,18 @@ data class Anime(
|
|||
val nextEpisodeAiringAt: Long
|
||||
get() = (viewerFlags and ANIME_AIRING_TIME_MASK).removeHexZeros(zeros = 6)
|
||||
|
||||
val unseenFilter: TriStateFilter
|
||||
val unseenFilter: TriState
|
||||
get() = when (unseenFilterRaw) {
|
||||
EPISODE_SHOW_UNSEEN -> TriStateFilter.ENABLED_IS
|
||||
EPISODE_SHOW_SEEN -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
EPISODE_SHOW_UNSEEN -> TriState.ENABLED_IS
|
||||
EPISODE_SHOW_SEEN -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
|
||||
val bookmarkedFilter: TriStateFilter
|
||||
val bookmarkedFilter: TriState
|
||||
get() = when (bookmarkedFilterRaw) {
|
||||
EPISODE_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
||||
EPISODE_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
EPISODE_SHOW_BOOKMARKED -> TriState.ENABLED_IS
|
||||
EPISODE_SHOW_NOT_BOOKMARKED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package tachiyomi.domain.entries.manga.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import java.io.Serializable
|
||||
|
||||
data class Manga(
|
||||
|
@ -44,18 +44,18 @@ data class Manga(
|
|||
val bookmarkedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
|
||||
|
||||
val unreadFilter: TriStateFilter
|
||||
val unreadFilter: TriState
|
||||
get() = when (unreadFilterRaw) {
|
||||
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
CHAPTER_SHOW_UNREAD -> TriState.ENABLED_IS
|
||||
CHAPTER_SHOW_READ -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
|
||||
val bookmarkedFilter: TriStateFilter
|
||||
val bookmarkedFilter: TriState
|
||||
get() = when (bookmarkedFilterRaw) {
|
||||
CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
CHAPTER_SHOW_BOOKMARKED -> TriState.ENABLED_IS
|
||||
CHAPTER_SHOW_NOT_BOOKMARKED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package tachiyomi.domain.library.service
|
||||
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.core.preference.getEnum
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.library.anime.model.AnimeLibrarySort
|
||||
|
@ -110,60 +110,60 @@ class LibraryPreferences(
|
|||
// Mixture Filter
|
||||
|
||||
fun filterDownloadedAnime() =
|
||||
preferenceStore.getEnum("pref_filter_animelib_downloaded_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_downloaded_v2", TriState.DISABLED)
|
||||
|
||||
fun filterDownloadedManga() =
|
||||
preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriState.DISABLED)
|
||||
|
||||
fun filterUnseen() =
|
||||
preferenceStore.getEnum("pref_filter_animelib_unread_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_unread_v2", TriState.DISABLED)
|
||||
|
||||
fun filterUnread() =
|
||||
preferenceStore.getEnum("pref_filter_library_unread_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_unread_v2", TriState.DISABLED)
|
||||
|
||||
fun filterStartedAnime() =
|
||||
preferenceStore.getEnum("pref_filter_animelib_started_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_started_v2", TriState.DISABLED)
|
||||
|
||||
fun filterStartedManga() =
|
||||
preferenceStore.getEnum("pref_filter_library_started_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_started_v2", TriState.DISABLED)
|
||||
|
||||
fun filterBookmarkedAnime() =
|
||||
preferenceStore.getEnum("pref_filter_animelib_bookmarked_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_bookmarked_v2", TriState.DISABLED)
|
||||
|
||||
fun filterBookmarkedManga() =
|
||||
preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriState.DISABLED)
|
||||
|
||||
fun filterCompletedAnime() =
|
||||
preferenceStore.getEnum("pref_filter_animelib_completed_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_completed_v2", TriState.DISABLED)
|
||||
|
||||
fun filterCompletedManga() =
|
||||
preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_completed_v2", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalCustomAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_custom", TriStateFilter.DISABLED)
|
||||
fun filterIntervalCustomAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_custom", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalCustomManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_custom", TriStateFilter.DISABLED)
|
||||
fun filterIntervalCustomManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_custom", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalLongAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_long", TriStateFilter.DISABLED)
|
||||
fun filterIntervalLongAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_long", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalLongManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_long", TriStateFilter.DISABLED)
|
||||
fun filterIntervalLongManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_long", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalLateAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_late", TriStateFilter.DISABLED)
|
||||
fun filterIntervalLateAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_late", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalLateManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_late", TriStateFilter.DISABLED)
|
||||
fun filterIntervalLateManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_late", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalDroppedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_dropped", TriStateFilter.DISABLED)
|
||||
fun filterIntervalDroppedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_dropped", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalDroppedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_dropped", TriStateFilter.DISABLED)
|
||||
fun filterIntervalDroppedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_dropped", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalPassedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_passed", TriStateFilter.DISABLED)
|
||||
fun filterIntervalPassedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_passed", TriState.DISABLED)
|
||||
|
||||
fun filterIntervalPassedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_passed", TriStateFilter.DISABLED)
|
||||
fun filterIntervalPassedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_passed", TriState.DISABLED)
|
||||
|
||||
fun filterTrackedAnime(id: Int) =
|
||||
preferenceStore.getEnum("pref_filter_animelib_tracked_${id}_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_animelib_tracked_${id}_v2", TriState.DISABLED)
|
||||
|
||||
fun filterTrackedManga(id: Int) =
|
||||
preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED)
|
||||
preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriState.DISABLED)
|
||||
|
||||
// Mixture Update Count
|
||||
|
||||
|
|
|
@ -373,7 +373,7 @@
|
|||
<string name="tapping_inverted_vertical">Vertical</string>
|
||||
<string name="tapping_inverted_both">Both</string>
|
||||
<string name="pref_reader_actions">Actions</string>
|
||||
<string name="pref_read_with_long_tap">Show on long tap</string>
|
||||
<string name="pref_read_with_long_tap">Show on actions long tap</string>
|
||||
<string name="pref_create_folder_per_manga">Save pages into separate folders</string>
|
||||
<string name="pref_create_folder_per_manga_summary">Creates folders according to entries\' title</string>
|
||||
<string name="pref_reader_theme">Background color</string>
|
||||
|
@ -431,12 +431,6 @@
|
|||
<string name="pref_category_reading_mode">Reading mode</string>
|
||||
<string name="pref_category_reading">Reading</string>
|
||||
<string name="pref_webtoon_side_padding">Side padding</string>
|
||||
<string name="webtoon_side_padding_0">None</string>
|
||||
<string name="webtoon_side_padding_5">5%</string>
|
||||
<string name="webtoon_side_padding_10">10%</string>
|
||||
<string name="webtoon_side_padding_15">15%</string>
|
||||
<string name="webtoon_side_padding_20">20%</string>
|
||||
<string name="webtoon_side_padding_25">25%</string>
|
||||
<string name="pref_hide_threshold">Sensitivity for hiding menu on scroll</string>
|
||||
<string name="pref_highest">Highest</string>
|
||||
<string name="pref_high">High</string>
|
||||
|
@ -479,7 +473,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 -->
|
||||
|
@ -620,6 +613,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>
|
||||
|
|
|
@ -21,6 +21,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
|
||||
// Compose
|
||||
implementation(platform(compose.bom))
|
||||
implementation(compose.activity)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package tachiyomi.presentation.core.components
|
||||
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -10,22 +12,44 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.outlined.AddCircle
|
||||
import androidx.compose.material.icons.outlined.RemoveCircle
|
||||
import androidx.compose.material.icons.rounded.CheckBox
|
||||
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||
import androidx.compose.material.icons.rounded.DisabledByDefault
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
|
||||
object SettingsItemsPaddings {
|
||||
|
@ -174,6 +198,274 @@ fun SliderItem(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectItem(
|
||||
label: String,
|
||||
options: Array<out Any?>,
|
||||
selectedIndex: Int,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
label = { Text(text = label) },
|
||||
value = options[selectedIndex].toString(),
|
||||
onValueChange = {},
|
||||
enabled = false,
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded,
|
||||
)
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(
|
||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
options.forEachIndexed { index, text ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text.toString()) },
|
||||
onClick = {
|
||||
onSelect(index)
|
||||
expanded = false
|
||||
},
|
||||
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TriStateItem(
|
||||
label: String,
|
||||
state: TriState,
|
||||
enabled: Boolean = true,
|
||||
onClick: ((TriState) -> Unit)?,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
enabled = enabled && onClick != null,
|
||||
onClick = {
|
||||
when (state) {
|
||||
TriState.DISABLED -> onClick?.invoke(TriState.ENABLED_IS)
|
||||
TriState.ENABLED_IS -> onClick?.invoke(TriState.ENABLED_NOT)
|
||||
TriState.ENABLED_NOT -> onClick?.invoke(TriState.DISABLED)
|
||||
}
|
||||
},
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled
|
||||
|
||||
Icon(
|
||||
imageVector = when (state) {
|
||||
TriState.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank
|
||||
TriState.ENABLED_IS -> Icons.Rounded.CheckBox
|
||||
TriState.ENABLED_NOT -> Icons.Rounded.DisabledByDefault
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = if (!enabled || state == TriState.DISABLED) {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha)
|
||||
} else {
|
||||
when (onClick) {
|
||||
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled)
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> SelectItem(
|
||||
label: String,
|
||||
options: Array<T>,
|
||||
selectedIndex: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
onSelect: (Int) -> Unit,
|
||||
toString: (T) -> String = { it.toString() },
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
modifier = modifier,
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = SettingsItemsPaddings.Horizontal,
|
||||
vertical = SettingsItemsPaddings.Vertical,
|
||||
),
|
||||
label = { Text(text = label) },
|
||||
value = toString(options[selectedIndex]),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded,
|
||||
)
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
options.forEachIndexed { index, option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(toString(option)) },
|
||||
onClick = {
|
||||
onSelect(index)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RepeatingIconButton(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
maxDelayMillis: Long = 750,
|
||||
minDelayMillis: Long = 5,
|
||||
delayDecayFactor: Float = .25f,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val currentClickListener by rememberUpdatedState(onClick)
|
||||
var pressed by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
modifier = modifier.pointerInteropFilter {
|
||||
pressed = when (it.action) {
|
||||
MotionEvent.ACTION_DOWN -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
onClick = {},
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource,
|
||||
content = content,
|
||||
)
|
||||
|
||||
LaunchedEffect(pressed, enabled) {
|
||||
var currentDelayMillis = maxDelayMillis
|
||||
|
||||
while (enabled && pressed) {
|
||||
currentClickListener()
|
||||
delay(currentDelayMillis)
|
||||
currentDelayMillis =
|
||||
(currentDelayMillis - (currentDelayMillis * delayDecayFactor))
|
||||
.toLong().coerceAtLeast(minDelayMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OutlinedNumericChooser(
|
||||
label: String,
|
||||
placeholder: String,
|
||||
suffix: String,
|
||||
value: Int,
|
||||
step: Int,
|
||||
min: Int? = null,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
) {
|
||||
var currentValue = value
|
||||
|
||||
val updateValue: (Boolean) -> Unit = {
|
||||
currentValue += if (it) step else -step
|
||||
|
||||
if (min != null) currentValue = if (currentValue < min) min else currentValue
|
||||
|
||||
onValueChanged(currentValue)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RepeatingIconButton(
|
||||
onClick = { updateValue(false) },
|
||||
) { Icon(imageVector = Icons.Outlined.RemoveCircle, contentDescription = null) }
|
||||
|
||||
OutlinedTextField(
|
||||
value = "%d".format(currentValue),
|
||||
modifier = Modifier.widthIn(min = 140.dp),
|
||||
|
||||
onValueChange = {
|
||||
// Don't allow multiple decimal points, non-numeric characters, or leading zeros
|
||||
currentValue = it.trim().replace(Regex("[^-\\d.]"), "").toIntOrNull()
|
||||
?: currentValue
|
||||
onValueChanged(currentValue)
|
||||
},
|
||||
|
||||
label = { Text(text = label) },
|
||||
placeholder = { Text(text = placeholder) },
|
||||
suffix = { Text(text = suffix) },
|
||||
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
)
|
||||
|
||||
RepeatingIconButton(
|
||||
onClick = { updateValue(true) },
|
||||
) { Icon(imageVector = Icons.Outlined.AddCircle, contentDescription = null) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextItem(
|
||||
label: String,
|
||||
value: String,
|
||||
onChange: (String) -> Unit,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = 4.dp),
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
onValueChange = onChange,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BaseSettingsItem(
|
||||
label: String,
|
||||
|
@ -195,20 +487,3 @@ private fun BaseSettingsItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextItem(
|
||||
label: String,
|
||||
value: String,
|
||||
onChange: (String) -> Unit,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = 4.dp),
|
||||
label = { Text(text = label) },
|
||||
value = value,
|
||||
onValueChange = onChange,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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.TabRowDefaults.tabIndicatorOffset
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -51,8 +51,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 a new issue