Migrate source filter sheet to Compose (#9135)
This commit is contained in:
parent
36ae388332
commit
92132c59f5
36 changed files with 459 additions and 1305 deletions
|
@ -1,52 +1,32 @@
|
|||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.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.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.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
|
||||
@Composable
|
||||
fun HeadingItem(
|
||||
@StringRes labelRes: Int,
|
||||
) {
|
||||
HeadingItem(stringResource(labelRes))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HeadingItem(
|
||||
text: String,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.header,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
import tachiyomi.presentation.core.components.SettingsItemsPaddings
|
||||
|
||||
@Composable
|
||||
fun TriStateItem(
|
||||
|
@ -68,7 +48,7 @@ fun TriStateItem(
|
|||
},
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
|
@ -99,87 +79,50 @@ fun TriStateItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun SortItem(
|
||||
fun SelectItem(
|
||||
label: String,
|
||||
sortDescending: Boolean?,
|
||||
onClick: () -> Unit,
|
||||
options: Array<out Any?>,
|
||||
selectedIndex: Int,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
val arrowIcon = when (sortDescending) {
|
||||
true -> Icons.Default.ArrowDownward
|
||||
false -> Icons.Default.ArrowUpward
|
||||
null -> null
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
) {
|
||||
if (arrowIcon != null) {
|
||||
Icon(
|
||||
imageVector = arrowIcon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.menuAnchor()
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
label = { Text(text = label) },
|
||||
value = options[selectedIndex].toString(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded,
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CheckboxItem(
|
||||
label: String,
|
||||
checked: Boolean,
|
||||
onClick: () -> Unit,
|
||||
ExposedDropdownMenu(
|
||||
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
options.forEachIndexed { index, text ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text.toString()) },
|
||||
onClick = {
|
||||
onSelect(index)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RadioItem(
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selected,
|
||||
onClick = null,
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.presentation.components.CheckboxItem
|
||||
import eu.kanade.presentation.components.HeadingItem
|
||||
import eu.kanade.presentation.components.RadioItem
|
||||
import eu.kanade.presentation.components.SortItem
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.components.TriStateItem
|
||||
|
@ -27,6 +23,10 @@ import tachiyomi.domain.library.model.LibrarySort
|
|||
import tachiyomi.domain.library.model.display
|
||||
import tachiyomi.domain.library.model.sort
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
|
||||
@Composable
|
||||
fun LibrarySettingsDialog(
|
||||
|
|
|
@ -25,14 +25,14 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.domain.manga.model.forceDownloaded
|
||||
import eu.kanade.presentation.components.RadioItem
|
||||
import eu.kanade.presentation.components.SortItem
|
||||
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.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
import tachiyomi.presentation.core.components.RadioItem
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
|
||||
@Composable
|
||||
fun ChapterSettingsDialog(
|
||||
|
|
|
@ -8,13 +8,11 @@ import androidx.compose.material3.SnackbarHost
|
|||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
|
@ -45,7 +43,6 @@ data class SourceSearchScreen(
|
|||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
|
@ -123,9 +120,5 @@ data class SourceSearchScreen(
|
|||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.filters) {
|
||||
screenModel.initFilterSheet(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -93,7 +92,6 @@ data class BrowseSourceScreen(
|
|||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
@ -231,7 +229,21 @@ data class BrowseSourceScreen(
|
|||
|
||||
val onDismissRequest = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is BrowseSourceScreenModel.Dialog.Migrate -> {}
|
||||
is BrowseSourceScreenModel.Dialog.Filter -> {
|
||||
SourceFilterDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
filters = state.filters,
|
||||
onReset = {
|
||||
screenModel.resetFilters()
|
||||
},
|
||||
onFilter = {
|
||||
screenModel.search(filters = state.filters)
|
||||
},
|
||||
onUpdate = {
|
||||
screenModel.setFilters(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
@ -259,13 +271,10 @@ data class BrowseSourceScreen(
|
|||
},
|
||||
)
|
||||
}
|
||||
is BrowseSourceScreenModel.Dialog.Migrate -> {}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.filters) {
|
||||
screenModel.initFilterSheet(context)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
queryEvent.receiveAsFlow()
|
||||
.collectLatest {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
@ -14,7 +13,6 @@ import androidx.paging.filter
|
|||
import androidx.paging.map
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.core.prefs.asState
|
||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||
|
@ -33,19 +31,6 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
|||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.HeaderItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.SelectItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.SelectSectionItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.SeparatorItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.SortGroup
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.SortItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
@ -125,11 +110,6 @@ class BrowseSourceScreenModel(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sheet containing filter items.
|
||||
*/
|
||||
private var filterSheet: SourceFilterSheet? = null
|
||||
|
||||
/**
|
||||
* Flow of Pager flow tied to [State.listing]
|
||||
*/
|
||||
|
@ -175,6 +155,16 @@ class BrowseSourceScreenModel(
|
|||
mutableState.update { it.copy(listing = listing) }
|
||||
}
|
||||
|
||||
fun setFilters(filters: FilterList) {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
filters = filters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun search(query: String? = null, filters: FilterList? = null) {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
|
@ -350,7 +340,7 @@ class BrowseSourceScreenModel(
|
|||
return getDuplicateLibraryManga.await(manga.title)
|
||||
}
|
||||
|
||||
fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||
}
|
||||
|
||||
|
@ -364,7 +354,7 @@ class BrowseSourceScreenModel(
|
|||
}
|
||||
|
||||
fun openFilterSheet() {
|
||||
filterSheet?.show()
|
||||
setDialog(Dialog.Filter)
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
|
@ -375,23 +365,6 @@ class BrowseSourceScreenModel(
|
|||
mutableState.update { it.copy(toolbarQuery = query) }
|
||||
}
|
||||
|
||||
fun initFilterSheet(context: Context) {
|
||||
if (state.value.filters.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
filterSheet = SourceFilterSheet(
|
||||
context = context,
|
||||
onFilterClicked = { search(filters = state.value.filters) },
|
||||
onResetClicked = {
|
||||
resetFilters()
|
||||
filterSheet?.setFilters(state.value.filterItems)
|
||||
},
|
||||
)
|
||||
|
||||
filterSheet?.setFilters(state.value.filterItems)
|
||||
}
|
||||
|
||||
sealed class Listing(open val query: String?, open val filters: FilterList) {
|
||||
object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
||||
object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
||||
|
@ -409,6 +382,7 @@ class BrowseSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object Filter : Dialog()
|
||||
data class RemoveManga(val manga: Manga) : Dialog()
|
||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
|
||||
data class ChangeMangaCategory(
|
||||
|
@ -425,43 +399,6 @@ class BrowseSourceScreenModel(
|
|||
val toolbarQuery: String? = null,
|
||||
val dialog: Dialog? = null,
|
||||
) {
|
||||
val filterItems get() = filters.toItems()
|
||||
val isUserQuery get() = listing is Listing.Search && !listing.query.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun FilterList.toItems(): List<IFlexible<*>> {
|
||||
return mapNotNull { filter ->
|
||||
when (filter) {
|
||||
is SourceModelFilter.Header -> HeaderItem(filter)
|
||||
is SourceModelFilter.Separator -> SeparatorItem(filter)
|
||||
is SourceModelFilter.CheckBox -> CheckboxItem(filter)
|
||||
is SourceModelFilter.TriState -> TriStateItem(filter)
|
||||
is SourceModelFilter.Text -> TextItem(filter)
|
||||
is SourceModelFilter.Select<*> -> SelectItem(filter)
|
||||
is SourceModelFilter.Group<*> -> {
|
||||
val group = GroupItem(filter)
|
||||
val subItems = filter.state.mapNotNull {
|
||||
when (it) {
|
||||
is SourceModelFilter.CheckBox -> CheckboxSectionItem(it)
|
||||
is SourceModelFilter.TriState -> TriStateSectionItem(it)
|
||||
is SourceModelFilter.Text -> TextSectionItem(it)
|
||||
is SourceModelFilter.Select<*> -> SelectSectionItem(it)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
subItems.forEach { it.header = group }
|
||||
group.subItems = subItems
|
||||
group
|
||||
}
|
||||
is SourceModelFilter.Sort -> {
|
||||
val group = SortGroup(filter)
|
||||
val subItems = filter.values.map {
|
||||
SortItem(it, group)
|
||||
}
|
||||
group.subItems = subItems
|
||||
group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
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 eu.kanade.tachiyomi.widget.TriState
|
||||
import eu.kanade.tachiyomi.widget.toTriStateFilter
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.CollapsibleBox
|
||||
import tachiyomi.presentation.core.components.HeadingItem
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
import tachiyomi.presentation.core.components.SortItem
|
||||
import tachiyomi.presentation.core.components.TextItem
|
||||
import tachiyomi.presentation.core.components.material.Button
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
|
||||
@Composable
|
||||
fun SourceFilterDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
filters: FilterList,
|
||||
onReset: () -> Unit,
|
||||
onFilter: () -> Unit,
|
||||
onUpdate: (FilterList) -> Unit,
|
||||
) {
|
||||
val updateFilters = { onUpdate(filters) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) { contentPadding ->
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
stickyHeader {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
TextButton(onClick = onReset) {
|
||||
Text(
|
||||
text = stringResource(R.string.action_reset),
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Button(onClick = onFilter) {
|
||||
Text(stringResource(R.string.action_filter))
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
|
||||
items(filters) {
|
||||
FilterItem(it, updateFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit) {
|
||||
when (filter) {
|
||||
is Filter.Header -> {
|
||||
HeadingItem(filter.name)
|
||||
}
|
||||
is Filter.Separator -> {
|
||||
Divider()
|
||||
}
|
||||
is Filter.CheckBox -> {
|
||||
CheckboxItem(
|
||||
label = filter.name,
|
||||
checked = filter.state,
|
||||
) {
|
||||
filter.state = !filter.state
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
is Filter.TriState -> {
|
||||
TriStateItem(
|
||||
label = filter.name,
|
||||
state = filter.state.toTriStateFilter(),
|
||||
) {
|
||||
filter.state = TriState.valueOf(filter.state).next().value
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
is Filter.Text -> {
|
||||
TextItem(
|
||||
label = filter.name,
|
||||
value = filter.state,
|
||||
) {
|
||||
filter.state = it
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
is Filter.Select<*> -> {
|
||||
SelectItem(
|
||||
label = filter.name,
|
||||
options = filter.values,
|
||||
selectedIndex = filter.state,
|
||||
) {
|
||||
filter.state = it
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
is Filter.Sort -> {
|
||||
CollapsibleBox(
|
||||
heading = filter.name,
|
||||
) {
|
||||
Column {
|
||||
filter.values.mapIndexed { index, item ->
|
||||
SortItem(
|
||||
label = item,
|
||||
sortDescending = filter.state?.ascending?.not()
|
||||
?.takeIf { index == filter.state?.index },
|
||||
) {
|
||||
val ascending = if (index == filter.state?.index) {
|
||||
!filter.state!!.ascending
|
||||
} else {
|
||||
filter.state!!.ascending
|
||||
}
|
||||
filter.state = Filter.Sort.Selection(
|
||||
index = index,
|
||||
ascending = ascending,
|
||||
)
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is Filter.Group<*> -> {
|
||||
CollapsibleBox(
|
||||
heading = filter.name,
|
||||
) {
|
||||
Column {
|
||||
filter.state
|
||||
.filterIsInstance<Filter<*>>()
|
||||
.map { FilterItem(filter = it, onUpdate = onUpdate) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
||||
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
|
||||
class SourceFilterSheet(
|
||||
context: Context,
|
||||
private val onFilterClicked: () -> Unit,
|
||||
private val onResetClicked: () -> Unit,
|
||||
) : BaseBottomSheetDialog(context) {
|
||||
|
||||
private var filterNavView: FilterNavigationView = FilterNavigationView(context)
|
||||
|
||||
override fun createView(inflater: LayoutInflater): View {
|
||||
filterNavView.onFilterClicked = {
|
||||
onFilterClicked()
|
||||
this.dismiss()
|
||||
}
|
||||
filterNavView.onResetClicked = onResetClicked
|
||||
|
||||
return filterNavView
|
||||
}
|
||||
|
||||
fun setFilters(items: List<IFlexible<*>>) {
|
||||
filterNavView.adapter.updateDataSet(items)
|
||||
}
|
||||
|
||||
class FilterNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
) :
|
||||
SimpleNavigationView(context, attrs) {
|
||||
|
||||
var onFilterClicked = {}
|
||||
var onResetClicked = {}
|
||||
|
||||
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
||||
.setDisplayHeadersAtStartUp(true)
|
||||
|
||||
private val binding = SourceFilterSheetBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
null,
|
||||
false,
|
||||
)
|
||||
|
||||
init {
|
||||
recycler.adapter = adapter
|
||||
recycler.setHasFixedSize(true)
|
||||
(binding.root.getChildAt(1) as ViewGroup).addView(recycler)
|
||||
addView(binding.root)
|
||||
binding.filterBtn.setOnClickListener { onFilterClicked() }
|
||||
binding.resetBtn.setOnClickListener { onResetClicked() }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<CheckboxItem.Holder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_checkbox
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
val view = holder.check
|
||||
view.text = filter.name
|
||||
view.isChecked = filter.state
|
||||
holder.itemView.setOnClickListener {
|
||||
view.toggle()
|
||||
filter.state = view.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as CheckboxItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
val check: CheckBox = itemView.findViewById(R.id.nav_view_item)
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.flexibleadapter.items.ISectionable
|
||||
import eu.davidea.viewholders.ExpandableViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||
|
||||
class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() {
|
||||
|
||||
init {
|
||||
isExpanded = false
|
||||
}
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_group
|
||||
}
|
||||
|
||||
override fun getItemViewType(): Int {
|
||||
return 101
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
holder.title.text = filter.name
|
||||
|
||||
holder.icon.setVectorCompat(
|
||||
if (isExpanded) {
|
||||
R.drawable.ic_expand_less_24dp
|
||||
} else {
|
||||
R.drawable.ic_expand_more_24dp
|
||||
},
|
||||
)
|
||||
|
||||
holder.itemView.setOnClickListener(holder)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as GroupItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
open class Holder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter, true) {
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
val icon: ImageView = itemView.findViewById(R.id.expand_icon)
|
||||
|
||||
override fun shouldNotifyParentOnClick(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.R
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.design_navigation_item_subheader
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
val view = holder.itemView as TextView
|
||||
view.text = filter.name
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as HeaderItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import eu.davidea.flexibleadapter.items.ISectionable
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable<TriStateItem.Holder, GroupItem> {
|
||||
|
||||
private var head: GroupItem? = null
|
||||
|
||||
override fun getHeader(): GroupItem? = head
|
||||
|
||||
override fun setHeader(header: GroupItem?) {
|
||||
head = header
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TriStateSectionItem
|
||||
if (head != other.head) return false
|
||||
return filter == other.filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode() + (head?.hashCode() ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable<TextItem.Holder, GroupItem> {
|
||||
|
||||
private var head: GroupItem? = null
|
||||
|
||||
override fun getHeader(): GroupItem? = head
|
||||
|
||||
override fun setHeader(header: GroupItem?) {
|
||||
head = header
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TextSectionItem
|
||||
if (head != other.head) return false
|
||||
return filter == other.filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode() + (head?.hashCode() ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISectionable<CheckboxItem.Holder, GroupItem> {
|
||||
|
||||
private var head: GroupItem? = null
|
||||
|
||||
override fun getHeader(): GroupItem? = head
|
||||
|
||||
override fun setHeader(header: GroupItem?) {
|
||||
head = header
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as CheckboxSectionItem
|
||||
if (head != other.head) return false
|
||||
return filter == other.filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode() + (head?.hashCode() ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISectionable<SelectItem.Holder, GroupItem> {
|
||||
|
||||
private var head: GroupItem? = null
|
||||
|
||||
override fun getHeader(): GroupItem? = head
|
||||
|
||||
override fun setHeader(header: GroupItem?) {
|
||||
head = header
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SelectSectionItem
|
||||
if (head != other.head) return false
|
||||
return filter == other.filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode() + (head?.hashCode() ?: 0)
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.widget.listener.IgnoreFirstSpinnerListener
|
||||
|
||||
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_spinner
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
holder.text.text = filter.name + ": "
|
||||
|
||||
val spinner = holder.spinner
|
||||
spinner.prompt = filter.name
|
||||
spinner.adapter = ArrayAdapter<Any>(
|
||||
holder.itemView.context,
|
||||
android.R.layout.simple_spinner_item,
|
||||
filter.values,
|
||||
).apply {
|
||||
setDropDownViewResource(R.layout.common_spinner_item)
|
||||
}
|
||||
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
|
||||
filter.state = pos
|
||||
}
|
||||
spinner.setSelection(filter.state)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as SelectItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
|
||||
val text: TextView = itemView.findViewById(R.id.nav_view_item_text)
|
||||
val spinner: Spinner = itemView.findViewById(R.id.nav_view_item)
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.R
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<SeparatorItem.Holder>() {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.design_navigation_item_separator
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as SeparatorItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter)
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.flexibleadapter.items.ISectionable
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||
|
||||
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {
|
||||
|
||||
init {
|
||||
isExpanded = false
|
||||
}
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_group
|
||||
}
|
||||
|
||||
override fun getItemViewType(): Int {
|
||||
return 100
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
holder.title.text = filter.name
|
||||
|
||||
holder.icon.setVectorCompat(
|
||||
if (isExpanded) {
|
||||
R.drawable.ic_expand_less_24dp
|
||||
} else {
|
||||
R.drawable.ic_expand_more_24dp
|
||||
},
|
||||
)
|
||||
|
||||
holder.itemView.setOnClickListener(holder)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as SortGroup).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : GroupItem.Holder(view, adapter)
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.CheckedTextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
|
||||
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_checkedtext
|
||||
}
|
||||
|
||||
override fun getItemViewType(): Int {
|
||||
return 102
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
val view = holder.text
|
||||
view.text = name
|
||||
val filter = group.filter
|
||||
|
||||
val i = filter.values.indexOf(name)
|
||||
|
||||
fun getIcon() = when (filter.state) {
|
||||
Filter.Sort.Selection(i, false) ->
|
||||
AppCompatResources.getDrawable(view.context, R.drawable.ic_arrow_down_white_32dp)
|
||||
?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) }
|
||||
Filter.Sort.Selection(i, true) ->
|
||||
AppCompatResources.getDrawable(view.context, R.drawable.ic_arrow_up_white_32dp)
|
||||
?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) }
|
||||
else -> AppCompatResources.getDrawable(view.context, R.drawable.empty_drawable_32dp)
|
||||
}
|
||||
|
||||
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||
holder.itemView.setOnClickListener {
|
||||
val pre = filter.state?.index ?: i
|
||||
if (pre != i) {
|
||||
filter.state = Filter.Sort.Selection(i, false)
|
||||
} else {
|
||||
filter.state = Filter.Sort.Selection(i, filter.state?.ascending == false)
|
||||
}
|
||||
|
||||
group.subItems.forEach { adapter.notifyItemChanged(adapter.getGlobalPositionOf(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
other as SortItem
|
||||
return name == other.name && group == other.group
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + group.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
val text: CheckedTextView = itemView.findViewById(R.id.nav_view_item)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.navigation_view_text
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
holder.wrapper.hint = filter.name
|
||||
holder.edit.setText(filter.state)
|
||||
holder.edit.doOnTextChanged { text, _, _, _ ->
|
||||
filter.state = text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as TextItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
val wrapper: TextInputLayout = itemView.findViewById(R.id.nav_view_item_wrapper)
|
||||
val edit: EditText = itemView.findViewById(R.id.nav_view_item)
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.CheckedTextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.R
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.R as TR
|
||||
|
||||
open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return TR.layout.navigation_view_checkedtext
|
||||
}
|
||||
|
||||
override fun getItemViewType(): Int {
|
||||
return 103
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
val view = holder.text
|
||||
view.text = filter.name
|
||||
|
||||
fun getIcon() = AppCompatResources.getDrawable(
|
||||
view.context,
|
||||
when (filter.state) {
|
||||
Filter.TriState.STATE_IGNORE -> TR.drawable.ic_check_box_outline_blank_24dp
|
||||
Filter.TriState.STATE_INCLUDE -> TR.drawable.ic_check_box_24dp
|
||||
Filter.TriState.STATE_EXCLUDE -> TR.drawable.ic_check_box_x_24dp
|
||||
else -> throw Exception("Unknown state")
|
||||
},
|
||||
)?.apply {
|
||||
val color = if (filter.state == Filter.TriState.STATE_IGNORE) {
|
||||
view.context.getResourceColor(R.attr.colorOnBackground, 0.38f)
|
||||
} else {
|
||||
view.context.getResourceColor(R.attr.colorPrimary)
|
||||
}
|
||||
|
||||
setTint(color)
|
||||
}
|
||||
|
||||
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||
holder.itemView.setOnClickListener {
|
||||
filter.state = (filter.state + 1) % 3
|
||||
view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return filter == (other as TriStateItem).filter
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return filter.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item)
|
||||
|
||||
init {
|
||||
// Align with native checkbox
|
||||
text.updatePadding(left = 4.dpToPx)
|
||||
text.compoundDrawablePadding = 20.dpToPx
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.RadioButton
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.TintTypedArray
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import eu.kanade.tachiyomi.util.view.inflate
|
||||
import eu.kanade.tachiyomi.R as TR
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
@SuppressLint("PrivateResource", "RestrictedApi")
|
||||
open class SimpleNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
/**
|
||||
* Recycler view containing all the items.
|
||||
*/
|
||||
protected val recycler = RecyclerView(context)
|
||||
|
||||
init {
|
||||
// Custom attributes
|
||||
val a = TintTypedArray.obtainStyledAttributes(
|
||||
context,
|
||||
attrs,
|
||||
R.styleable.NavigationView,
|
||||
defStyleAttr,
|
||||
R.style.Widget_Design_NavigationView,
|
||||
)
|
||||
|
||||
a.recycle()
|
||||
|
||||
recycler.layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Base view holder.
|
||||
*/
|
||||
abstract class Holder(view: View) : RecyclerView.ViewHolder(view)
|
||||
|
||||
/**
|
||||
* Separator view holder.
|
||||
*/
|
||||
class SeparatorHolder(parent: ViewGroup) :
|
||||
Holder(parent.inflate(R.layout.design_navigation_item_separator))
|
||||
|
||||
/**
|
||||
* Header view holder.
|
||||
*/
|
||||
class HeaderHolder(parent: ViewGroup) :
|
||||
Holder(parent.inflate(TR.layout.navigation_view_group)) {
|
||||
|
||||
val title: TextView = itemView.findViewById(TR.id.title)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clickable view holder.
|
||||
*/
|
||||
abstract class ClickableHolder(view: View, listener: OnClickListener?) : Holder(view) {
|
||||
init {
|
||||
itemView.setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio view holder.
|
||||
*/
|
||||
class RadioHolder(parent: ViewGroup, listener: OnClickListener?) :
|
||||
ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) {
|
||||
|
||||
val radio: RadioButton = itemView.findViewById(TR.id.nav_view_item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkbox view holder.
|
||||
*/
|
||||
class CheckboxHolder(parent: ViewGroup, listener: OnClickListener?) :
|
||||
ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) {
|
||||
|
||||
val check: CheckBox = itemView.findViewById(TR.id.nav_view_item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi state view holder.
|
||||
*/
|
||||
class MultiStateHolder(parent: ViewGroup, listener: OnClickListener?) :
|
||||
ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) {
|
||||
|
||||
val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item)
|
||||
}
|
||||
|
||||
class SpinnerHolder(parent: ViewGroup, listener: OnClickListener? = null) :
|
||||
ClickableHolder(parent.inflate(TR.layout.navigation_view_spinner), listener) {
|
||||
|
||||
val text: TextView = itemView.findViewById(TR.id.nav_view_item_text)
|
||||
val spinner: Spinner = itemView.findViewById(TR.id.nav_view_item)
|
||||
}
|
||||
|
||||
class EditTextHolder(parent: ViewGroup) :
|
||||
Holder(parent.inflate(TR.layout.navigation_view_text)) {
|
||||
|
||||
val wrapper: TextInputLayout = itemView.findViewById(TR.id.nav_view_item_wrapper)
|
||||
val edit: EditText = itemView.findViewById(TR.id.nav_view_item)
|
||||
}
|
||||
|
||||
protected companion object {
|
||||
const val VIEW_TYPE_HEADER = 100
|
||||
const val VIEW_TYPE_SEPARATOR = 101
|
||||
const val VIEW_TYPE_RADIO = 102
|
||||
const val VIEW_TYPE_CHECKBOX = 103
|
||||
const val VIEW_TYPE_MULTISTATE = 104
|
||||
const val VIEW_TYPE_TEXT = 105
|
||||
const val VIEW_TYPE_LIST = 106
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package eu.kanade.tachiyomi.widget.listener
|
||||
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemSelectedListener
|
||||
|
||||
class IgnoreFirstSpinnerListener(private val block: (Int) -> Unit) : OnItemSelectedListener {
|
||||
|
||||
private var firstEvent = true
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
if (!firstEvent) {
|
||||
block(position)
|
||||
} else {
|
||||
firstEvent = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<size
|
||||
android:width="32dp"
|
||||
android:height="32dp" />
|
||||
</shape>
|
|
@ -1,15 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<group
|
||||
android:pivotX="32"
|
||||
android:pivotY="32"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -1,15 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<group
|
||||
android:pivotX="32"
|
||||
android:pivotY="32"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -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="M10.6,16.2 L17.65,9.15 16.25,7.75 10.6,13.4 7.75,10.55 6.35,11.95ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21Z"/>
|
||||
</vector>
|
|
@ -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="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
|
@ -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="M19,3H16.3H7.7H5A2,2 0 0,0 3,5V7.7V16.4V19A2,2 0 0,0 5,21H7.7H16.4H19A2,2 0 0,0 21,19V16.3V7.7V5A2,2 0 0,0 19,3M15.6,17L12,13.4L8.4,17L7,15.6L10.6,12L7,8.4L8.4,7L12,10.6L15.6,7L17,8.4L13.4,12L17,15.6L15.6,17Z" />
|
||||
</vector>
|
|
@ -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="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z" />
|
||||
</vector>
|
|
@ -1,24 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/nav_view_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="false"
|
||||
android:gravity="center_vertical|start"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
tools:text="Title" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<CheckedTextView
|
||||
android:id="@+id/nav_view_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
tools:text="Title" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,29 +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="?attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader"
|
||||
tools:text="Header" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/expand_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:tint="?attr/colorOnBackground"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/nav_view_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="false"
|
||||
android:gravity="center_vertical|start"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_view_item_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="8dp"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="Filter:" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/nav_view_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|start"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:baselineAligned="false"
|
||||
android:focusable="true"
|
||||
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/nav_view_item_wrapper"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|start">
|
||||
|
||||
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
||||
android:id="@+id/nav_view_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,47 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/transparent_tabs_background"
|
||||
android:elevation="2dp"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="?attr/listPreferredItemPaddingStart">
|
||||
|
||||
<Button
|
||||
android:id="@+id/reset_btn"
|
||||
style="?attr/borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_reset"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/filter_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_filter"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_weight="1"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,56 @@
|
|||
package tachiyomi.presentation.core.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
|
||||
@Composable
|
||||
fun CollapsibleBox(
|
||||
heading: String,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { expanded = !expanded }
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = heading,
|
||||
style = MaterialTheme.typography.header,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package tachiyomi.presentation.core.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.presentation.core.theme.header
|
||||
|
||||
object SettingsItemsPaddings {
|
||||
val Horizontal = 24.dp
|
||||
val Vertical = 10.dp
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HeadingItem(
|
||||
@StringRes labelRes: Int,
|
||||
) {
|
||||
HeadingItem(stringResource(labelRes))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HeadingItem(
|
||||
text: String,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.header,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SortItem(
|
||||
label: String,
|
||||
sortDescending: Boolean?,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val arrowIcon = when (sortDescending) {
|
||||
true -> Icons.Default.ArrowDownward
|
||||
false -> Icons.Default.ArrowUpward
|
||||
null -> null
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
if (arrowIcon != null) {
|
||||
Icon(
|
||||
imageVector = arrowIcon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CheckboxItem(
|
||||
label: String,
|
||||
checked: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RadioItem(
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selected,
|
||||
onClick = null,
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
}
|
Reference in a new issue