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
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.icons.Icons
|
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.CheckBox
|
||||||
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||||
import androidx.compose.material.icons.rounded.DisabledByDefault
|
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.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.domain.manga.model.TriStateFilter
|
import tachiyomi.domain.manga.model.TriStateFilter
|
||||||
import tachiyomi.presentation.core.theme.header
|
import tachiyomi.presentation.core.components.SettingsItemsPaddings
|
||||||
|
|
||||||
@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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TriStateItem(
|
fun TriStateItem(
|
||||||
|
@ -68,7 +48,7 @@ fun TriStateItem(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
|
@ -99,87 +79,50 @@ fun TriStateItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SortItem(
|
fun SelectItem(
|
||||||
label: String,
|
label: String,
|
||||||
sortDescending: Boolean?,
|
options: Array<out Any?>,
|
||||||
onClick: () -> Unit,
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val arrowIcon = when (sortDescending) {
|
var expanded by remember { mutableStateOf(false) }
|
||||||
true -> Icons.Default.ArrowDownward
|
|
||||||
false -> Icons.Default.ArrowUpward
|
|
||||||
null -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
ExposedDropdownMenuBox(
|
||||||
modifier = Modifier
|
expanded = expanded,
|
||||||
.clickable(onClick = onClick)
|
onExpandedChange = { expanded = !expanded },
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
|
||||||
) {
|
) {
|
||||||
if (arrowIcon != null) {
|
OutlinedTextField(
|
||||||
Icon(
|
modifier = Modifier
|
||||||
imageVector = arrowIcon,
|
.menuAnchor()
|
||||||
contentDescription = null,
|
.fillMaxWidth()
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
.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))
|
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
ExposedDropdownMenu(
|
||||||
fun CheckboxItem(
|
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true),
|
||||||
label: String,
|
expanded = expanded,
|
||||||
checked: Boolean,
|
onDismissRequest = { expanded = false },
|
||||||
onClick: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
options.forEachIndexed { index, text ->
|
||||||
modifier = Modifier
|
DropdownMenuItem(
|
||||||
.clickable(onClick = onClick)
|
text = { Text(text.toString()) },
|
||||||
.fillMaxWidth()
|
onClick = {
|
||||||
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
onSelect(index)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
expanded = false
|
||||||
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 = 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.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
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.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.presentation.components.TriStateItem
|
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.display
|
||||||
import tachiyomi.domain.library.model.sort
|
import tachiyomi.domain.library.model.sort
|
||||||
import tachiyomi.domain.manga.model.TriStateFilter
|
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
|
@Composable
|
||||||
fun LibrarySettingsDialog(
|
fun LibrarySettingsDialog(
|
||||||
|
|
|
@ -25,14 +25,14 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
import eu.kanade.domain.manga.model.forceDownloaded
|
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.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.presentation.components.TriStateItem
|
import eu.kanade.presentation.components.TriStateItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.TriStateFilter
|
import tachiyomi.domain.manga.model.TriStateFilter
|
||||||
|
import tachiyomi.presentation.core.components.RadioItem
|
||||||
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterSettingsDialog(
|
fun ChapterSettingsDialog(
|
||||||
|
|
|
@ -8,13 +8,11 @@ import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
@ -45,7 +43,6 @@ data class SourceSearchScreen(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
@ -123,9 +120,5 @@ data class SourceSearchScreen(
|
||||||
}
|
}
|
||||||
else -> {}
|
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.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -93,7 +92,6 @@ data class BrowseSourceScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
@ -231,7 +229,21 @@ data class BrowseSourceScreen(
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.setDialog(null) }
|
val onDismissRequest = { screenModel.setDialog(null) }
|
||||||
when (val dialog = state.dialog) {
|
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 -> {
|
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
@ -259,13 +271,10 @@ data class BrowseSourceScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is BrowseSourceScreenModel.Dialog.Migrate -> {}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.filters) {
|
|
||||||
screenModel.initFilterSheet(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
queryEvent.receiveAsFlow()
|
queryEvent.receiveAsFlow()
|
||||||
.collectLatest {
|
.collectLatest {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
@ -14,7 +13,6 @@ import androidx.paging.filter
|
||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
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.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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 eu.kanade.tachiyomi.util.removeCovers
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
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]
|
* Flow of Pager flow tied to [State.listing]
|
||||||
*/
|
*/
|
||||||
|
@ -175,6 +155,16 @@ class BrowseSourceScreenModel(
|
||||||
mutableState.update { it.copy(listing = listing) }
|
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) {
|
fun search(query: String? = null, filters: FilterList? = null) {
|
||||||
if (source !is CatalogueSource) return
|
if (source !is CatalogueSource) return
|
||||||
|
|
||||||
|
@ -350,7 +340,7 @@ class BrowseSourceScreenModel(
|
||||||
return getDuplicateLibraryManga.await(manga.title)
|
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 })
|
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +354,7 @@ class BrowseSourceScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openFilterSheet() {
|
fun openFilterSheet() {
|
||||||
filterSheet?.show()
|
setDialog(Dialog.Filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDialog(dialog: Dialog?) {
|
fun setDialog(dialog: Dialog?) {
|
||||||
|
@ -375,23 +365,6 @@ class BrowseSourceScreenModel(
|
||||||
mutableState.update { it.copy(toolbarQuery = query) }
|
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) {
|
sealed class Listing(open val query: String?, open val filters: FilterList) {
|
||||||
object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
||||||
object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
||||||
|
@ -409,6 +382,7 @@ class BrowseSourceScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Dialog {
|
sealed class Dialog {
|
||||||
|
object Filter : Dialog()
|
||||||
data class RemoveManga(val manga: Manga) : Dialog()
|
data class RemoveManga(val manga: Manga) : Dialog()
|
||||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
|
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
|
||||||
data class ChangeMangaCategory(
|
data class ChangeMangaCategory(
|
||||||
|
@ -425,43 +399,6 @@ class BrowseSourceScreenModel(
|
||||||
val toolbarQuery: String? = null,
|
val toolbarQuery: String? = null,
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
) {
|
) {
|
||||||
val filterItems get() = filters.toItems()
|
|
||||||
val isUserQuery get() = listing is Listing.Search && !listing.query.isNullOrEmpty()
|
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