Use Voyager on BrowseSource and SourceSearch screen (#8650)
Some navigation janks will be dealt with when the migration is complete
This commit is contained in:
parent
8eda4df71f
commit
94d1b68598
@ -1,213 +1,37 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Favorite
|
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.FilterChipDefaults
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import eu.kanade.data.source.NoResultsException
|
import eu.kanade.data.source.NoResultsException
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceList
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
|
|
||||||
import eu.kanade.presentation.components.AppStateBanners
|
|
||||||
import eu.kanade.presentation.components.Divider
|
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.EmptyScreenAction
|
import eu.kanade.presentation.components.EmptyScreenAction
|
||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BrowseSourceScreen(
|
|
||||||
presenter: BrowseSourcePresenter,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
openFilterSheet: () -> Unit,
|
|
||||||
onMangaClick: (Manga) -> Unit,
|
|
||||||
onMangaLongClick: (Manga) -> Unit,
|
|
||||||
onWebViewClick: () -> Unit,
|
|
||||||
incognitoMode: Boolean,
|
|
||||||
downloadedOnlyMode: Boolean,
|
|
||||||
) {
|
|
||||||
val columns by presenter.getColumnsPreferenceForCurrentOrientation()
|
|
||||||
|
|
||||||
val mangaList = presenter.getMangaList().collectAsLazyPagingItems()
|
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
|
|
||||||
val onHelpClick = {
|
|
||||||
uriHandler.openUri(LocalSource.HELP_URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
|
||||||
BrowseSourceToolbar(
|
|
||||||
state = presenter,
|
|
||||||
source = presenter.source,
|
|
||||||
displayMode = presenter.displayMode,
|
|
||||||
onDisplayModeChange = { presenter.displayMode = it },
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = onHelpClick,
|
|
||||||
onSearch = { presenter.search(it) },
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.horizontalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 8.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter == BrowseSourcePresenter.Filter.Popular,
|
|
||||||
onClick = {
|
|
||||||
presenter.reset()
|
|
||||||
presenter.search(GetRemoteManga.QUERY_POPULAR)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Favorite,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.popular))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (presenter.source?.supportsLatest == true) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter == BrowseSourcePresenter.Filter.Latest,
|
|
||||||
onClick = {
|
|
||||||
presenter.reset()
|
|
||||||
presenter.search(GetRemoteManga.QUERY_LATEST)
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.NewReleases,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.latest))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (presenter.filters.isNotEmpty()) {
|
|
||||||
FilterChip(
|
|
||||||
selected = presenter.currentFilter is BrowseSourcePresenter.Filter.UserInput,
|
|
||||||
onClick = openFilterSheet,
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.FilterList,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(R.string.action_filter))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
AppStateBanners(downloadedOnlyMode, incognitoMode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
snackbarHost = {
|
|
||||||
SnackbarHost(hostState = snackbarHostState)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
BrowseSourceContent(
|
|
||||||
state = presenter,
|
|
||||||
mangaList = mangaList,
|
|
||||||
getMangaState = { presenter.getManga(it) },
|
|
||||||
columns = columns,
|
|
||||||
displayMode = presenter.displayMode,
|
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
contentPadding = paddingValues,
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
|
||||||
onLocalSourceHelpClick = onHelpClick,
|
|
||||||
onMangaClick = onMangaClick,
|
|
||||||
onMangaLongClick = onMangaLongClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BrowseSourceFloatingActionButton(
|
|
||||||
modifier: Modifier = Modifier.navigationBarsPadding(),
|
|
||||||
isVisible: Boolean,
|
|
||||||
onFabClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(visible = isVisible) {
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
modifier = modifier,
|
|
||||||
text = { Text(text = stringResource(R.string.action_filter)) },
|
|
||||||
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
|
|
||||||
onClick = onFabClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceContent(
|
fun BrowseSourceContent(
|
||||||
state: BrowseSourceState,
|
source: CatalogueSource?,
|
||||||
mangaList: LazyPagingItems<Manga>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
|
||||||
columns: GridCells,
|
columns: GridCells,
|
||||||
displayMode: LibraryDisplayMode,
|
displayMode: LibraryDisplayMode,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
@ -249,7 +73,7 @@ fun BrowseSourceContent(
|
|||||||
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (state.source is LocalSource) {
|
actions = if (source is LocalSource) {
|
||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
@ -290,7 +114,6 @@ fun BrowseSourceContent(
|
|||||||
LibraryDisplayMode.ComfortableGrid -> {
|
LibraryDisplayMode.ComfortableGrid -> {
|
||||||
BrowseSourceComfortableGrid(
|
BrowseSourceComfortableGrid(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
@ -300,16 +123,14 @@ fun BrowseSourceContent(
|
|||||||
LibraryDisplayMode.List -> {
|
LibraryDisplayMode.List -> {
|
||||||
BrowseSourceList(
|
BrowseSourceList(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
onMangaLongClick = onMangaLongClick,
|
onMangaLongClick = onMangaLongClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||||
BrowseSourceCompactGrid(
|
BrowseSourceCompactGrid(
|
||||||
mangaList = mangaList,
|
mangaList = mangaList,
|
||||||
getMangaState = getMangaState,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onMangaClick = onMangaClick,
|
onMangaClick = onMangaClick,
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Filter
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.toItems
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
interface BrowseSourceState {
|
|
||||||
val source: CatalogueSource?
|
|
||||||
var searchQuery: String?
|
|
||||||
val currentFilter: Filter
|
|
||||||
val isUserQuery: Boolean
|
|
||||||
val filters: FilterList
|
|
||||||
val filterItems: List<IFlexible<*>>
|
|
||||||
var dialog: BrowseSourcePresenter.Dialog?
|
|
||||||
}
|
|
||||||
|
|
||||||
fun BrowseSourceState(initialQuery: String?): BrowseSourceState {
|
|
||||||
return when (val filter = Filter.valueOf(initialQuery ?: "")) {
|
|
||||||
Filter.Latest, Filter.Popular -> BrowseSourceStateImpl(initialCurrentFilter = filter)
|
|
||||||
is Filter.UserInput -> BrowseSourceStateImpl(initialQuery = initialQuery, initialCurrentFilter = filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowseSourceStateImpl(initialQuery: String? = null, initialCurrentFilter: Filter) : BrowseSourceState {
|
|
||||||
override var source: CatalogueSource? by mutableStateOf(null)
|
|
||||||
override var searchQuery: String? by mutableStateOf(initialQuery)
|
|
||||||
override var currentFilter: Filter by mutableStateOf(initialCurrentFilter)
|
|
||||||
override val isUserQuery: Boolean by derivedStateOf { currentFilter is Filter.UserInput && currentFilter.query.isNotEmpty() }
|
|
||||||
override var filters: FilterList by mutableStateOf(FilterList())
|
|
||||||
override val filterItems: List<IFlexible<*>> by derivedStateOf { filters.toItems() }
|
|
||||||
override var dialog: BrowseSourcePresenter.Dialog? by mutableStateOf(null)
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SourceSearchScreen(
|
|
||||||
presenter: BrowseSourcePresenter,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
onFabClick: () -> Unit,
|
|
||||||
onMangaClick: (Manga) -> Unit,
|
|
||||||
onWebViewClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
val columns by presenter.getColumnsPreferenceForCurrentOrientation()
|
|
||||||
|
|
||||||
val mangaList = presenter.getMangaList().collectAsLazyPagingItems()
|
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
|
|
||||||
val onHelpClick = {
|
|
||||||
uriHandler.openUri(LocalSource.HELP_URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
SearchToolbar(
|
|
||||||
searchQuery = presenter.searchQuery ?: "",
|
|
||||||
onChangeSearchQuery = { presenter.searchQuery = it },
|
|
||||||
onClickCloseSearch = navigateUp,
|
|
||||||
onSearch = { presenter.search(it) },
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
BrowseSourceFloatingActionButton(
|
|
||||||
isVisible = presenter.filters.isNotEmpty(),
|
|
||||||
onFabClick = onFabClick,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
snackbarHost = {
|
|
||||||
SnackbarHost(hostState = snackbarHostState)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
BrowseSourceContent(
|
|
||||||
state = presenter,
|
|
||||||
mangaList = mangaList,
|
|
||||||
getMangaState = { presenter.getManga(it) },
|
|
||||||
columns = columns,
|
|
||||||
displayMode = presenter.displayMode,
|
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
contentPadding = paddingValues,
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
|
||||||
onLocalSourceHelpClick = onHelpClick,
|
|
||||||
onMangaClick = onMangaClick,
|
|
||||||
onMangaLongClick = onMangaClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
@ -17,11 +17,11 @@ import eu.kanade.presentation.browse.InLibraryBadge
|
|||||||
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.MangaComfortableGridItem
|
import eu.kanade.presentation.components.MangaComfortableGridItem
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceComfortableGrid(
|
fun BrowseSourceComfortableGrid(
|
||||||
mangaList: LazyPagingItems<Manga>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
|
||||||
columns: GridCells,
|
columns: GridCells,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onMangaClick: (Manga) -> Unit,
|
onMangaClick: (Manga) -> Unit,
|
||||||
@ -40,8 +40,7 @@ fun BrowseSourceComfortableGrid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
items(mangaList.itemCount) { index ->
|
items(mangaList.itemCount) { index ->
|
||||||
val initialManga = mangaList[index] ?: return@items
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
val manga by getMangaState(initialManga)
|
|
||||||
BrowseSourceComfortableGridItem(
|
BrowseSourceComfortableGridItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClick = { onMangaClick(manga) },
|
onClick = { onMangaClick(manga) },
|
||||||
|
@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
@ -17,11 +17,11 @@ import eu.kanade.presentation.browse.InLibraryBadge
|
|||||||
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.MangaCompactGridItem
|
import eu.kanade.presentation.components.MangaCompactGridItem
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceCompactGrid(
|
fun BrowseSourceCompactGrid(
|
||||||
mangaList: LazyPagingItems<Manga>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
|
||||||
columns: GridCells,
|
columns: GridCells,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onMangaClick: (Manga) -> Unit,
|
onMangaClick: (Manga) -> Unit,
|
||||||
@ -40,8 +40,7 @@ fun BrowseSourceCompactGrid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
items(mangaList.itemCount) { index ->
|
items(mangaList.itemCount) { index ->
|
||||||
val initialManga = mangaList[index] ?: return@items
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
val manga by getMangaState(initialManga)
|
|
||||||
BrowseSourceCompactGridItem(
|
BrowseSourceCompactGridItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClick = { onMangaClick(manga) },
|
onClick = { onMangaClick(manga) },
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.presentation.browse.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
@ -15,11 +15,11 @@ import eu.kanade.presentation.components.CommonMangaItemDefaults
|
|||||||
import eu.kanade.presentation.components.LazyColumn
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
import eu.kanade.presentation.components.MangaListItem
|
import eu.kanade.presentation.components.MangaListItem
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceList(
|
fun BrowseSourceList(
|
||||||
mangaList: LazyPagingItems<Manga>,
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onMangaClick: (Manga) -> Unit,
|
onMangaClick: (Manga) -> Unit,
|
||||||
onMangaLongClick: (Manga) -> Unit,
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
@ -33,9 +33,9 @@ fun BrowseSourceList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(mangaList) { initialManga ->
|
items(mangaList) { mangaflow ->
|
||||||
initialManga ?: return@items
|
mangaflow ?: return@items
|
||||||
val manga by getMangaState(initialManga)
|
val manga by mangaflow.collectAsState()
|
||||||
BrowseSourceListItem(
|
BrowseSourceListItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClick = { onMangaClick(manga) },
|
onClick = { onMangaClick(manga) },
|
||||||
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.presentation.browse.BrowseSourceState
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.AppBarTitle
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
@ -27,7 +26,8 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceToolbar(
|
fun BrowseSourceToolbar(
|
||||||
state: BrowseSourceState,
|
searchQuery: String?,
|
||||||
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
source: CatalogueSource?,
|
source: CatalogueSource?,
|
||||||
displayMode: LibraryDisplayMode,
|
displayMode: LibraryDisplayMode,
|
||||||
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
||||||
@ -44,8 +44,8 @@ fun BrowseSourceToolbar(
|
|||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
titleContent = { AppBarTitle(title) },
|
titleContent = { AppBarTitle(title) },
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = searchQuery,
|
||||||
onChangeSearchQuery = { state.searchQuery = it },
|
onChangeSearchQuery = onSearchQueryChange,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
onClickCloseSearch = navigateUp,
|
onClickCloseSearch = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -2,26 +2,14 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.presentation.browse.SourceSearchScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
|
||||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||||
|
|
||||||
class SourceSearchController(
|
class SourceSearchController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
||||||
bundle: Bundle,
|
|
||||||
) : BrowseSourceController(bundle) {
|
|
||||||
|
|
||||||
constructor(manga: Manga? = null, source: CatalogueSource, searchQuery: String? = null) : this(
|
constructor(manga: Manga? = null, source: CatalogueSource, searchQuery: String? = null) : this(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@ -31,49 +19,16 @@ class SourceSearchController(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
private var oldManga: Manga? = args.getSerializableCompat(MANGA_KEY)
|
private var oldManga: Manga = args.getSerializableCompat(MANGA_KEY)!!
|
||||||
|
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
||||||
|
private val query = args.getString(SEARCH_QUERY_KEY)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
SourceSearchScreen(
|
Navigator(screen = SourceSearchScreen(oldManga, sourceId, query))
|
||||||
presenter = presenter,
|
|
||||||
navigateUp = { router.popCurrentController() },
|
|
||||||
onFabClick = { filterSheet?.show() },
|
|
||||||
onMangaClick = {
|
|
||||||
presenter.dialog = BrowseSourcePresenter.Dialog.Migrate(it)
|
|
||||||
},
|
|
||||||
onWebViewClick = f@{
|
|
||||||
val source = presenter.source as? HttpSource ?: return@f
|
|
||||||
activity?.let { context ->
|
|
||||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
when (val dialog = presenter.dialog) {
|
|
||||||
is BrowseSourcePresenter.Dialog.Migrate -> {
|
|
||||||
MigrateDialog(
|
|
||||||
oldManga = oldManga!!,
|
|
||||||
newManga = dialog.newManga,
|
|
||||||
// TODO: Move screen model down into Dialog when this screen is using Voyager
|
|
||||||
screenModel = remember { MigrateDialogScreenModel() },
|
|
||||||
onDismissRequest = { presenter.dialog = null },
|
|
||||||
onClickTitle = { router.pushController(MangaController(dialog.newManga.id)) },
|
|
||||||
onPopScreen = {
|
|
||||||
// TODO: Push to manga screen and remove this and the previous screen when it moves to Voyager
|
|
||||||
router.setRoot(BrowseController(toExtensions = false), R.id.nav_browse)
|
|
||||||
router.pushController(MangaController(dialog.newManga.id))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(presenter.filters) {
|
|
||||||
initFilterSheet()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val MANGA_KEY = "oldManga"
|
private const val MANGA_KEY = "oldManga"
|
||||||
|
private const val SOURCE_ID_KEY = "sourceId"
|
||||||
|
private const val SEARCH_QUERY_KEY = "searchQuery"
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.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
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||||
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
|
import eu.kanade.presentation.util.LocalRouter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.more.MoreController
|
||||||
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
|
||||||
|
data class SourceSearchScreen(
|
||||||
|
private val oldManga: Manga,
|
||||||
|
private val sourceId: Long,
|
||||||
|
private val query: String? = null,
|
||||||
|
) : Screen {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val router = LocalRouter.currentOrThrow
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
|
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
||||||
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
val navigateUp: () -> Unit = {
|
||||||
|
when {
|
||||||
|
navigator.canPop -> navigator.pop()
|
||||||
|
router.backstackSize > 1 -> router.popCurrentController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
SearchToolbar(
|
||||||
|
searchQuery = state.toolbarQuery ?: "",
|
||||||
|
onChangeSearchQuery = screenModel::setToolbarQuery,
|
||||||
|
onClickCloseSearch = navigateUp,
|
||||||
|
onSearch = { screenModel.search(it) },
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
AnimatedVisibility(visible = state.filters.isNotEmpty()) {
|
||||||
|
ExtendedFloatingActionButton(
|
||||||
|
text = { Text(text = stringResource(R.string.action_filter)) },
|
||||||
|
icon = { Icon(Icons.Outlined.FilterList, contentDescription = "") },
|
||||||
|
onClick = screenModel::openFilterSheet,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
|
) { paddingValues ->
|
||||||
|
val mangaList = remember(state.currentFilter) {
|
||||||
|
screenModel.getMangaListFlow(state.currentFilter)
|
||||||
|
}.collectAsLazyPagingItems()
|
||||||
|
val openMigrateDialog: (Manga) -> Unit = {
|
||||||
|
screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(it))
|
||||||
|
}
|
||||||
|
BrowseSourceContent(
|
||||||
|
source = screenModel.source,
|
||||||
|
mangaList = mangaList,
|
||||||
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
|
displayMode = screenModel.displayMode,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
onWebViewClick = {
|
||||||
|
val source = screenModel.source as? HttpSource ?: return@BrowseSourceContent
|
||||||
|
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
||||||
|
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
||||||
|
onMangaClick = openMigrateDialog,
|
||||||
|
onMangaLongClick = openMigrateDialog,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val dialog = state.dialog) {
|
||||||
|
is BrowseSourceScreenModel.Dialog.Migrate -> {
|
||||||
|
MigrateDialog(
|
||||||
|
oldManga = oldManga,
|
||||||
|
newManga = dialog.newManga,
|
||||||
|
screenModel = rememberScreenModel { MigrateDialogScreenModel() },
|
||||||
|
onDismissRequest = { screenModel.setDialog(null) },
|
||||||
|
onClickTitle = { router.pushController(MangaController(dialog.newManga.id)) },
|
||||||
|
onPopScreen = {
|
||||||
|
// TODO: Push to manga screen and remove this and the previous screen when it moves to Voyager
|
||||||
|
router.setRoot(BrowseController(toExtensions = false), R.id.nav_browse)
|
||||||
|
router.pushController(MangaController(dialog.newManga.id))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.filters) {
|
||||||
|
screenModel.initFilterSheet(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ fun Screen.sourcesTab(): TabContent {
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickItem = { source, query ->
|
onClickItem = { source, query ->
|
||||||
screenModel.onOpenSource(source)
|
screenModel.onOpenSource(source)
|
||||||
router.pushController(BrowseSourceController(source, query))
|
router.pushController(BrowseSourceController(source.id, query))
|
||||||
},
|
},
|
||||||
onClickPin = screenModel::togglePin,
|
onClickPin = screenModel::togglePin,
|
||||||
onLongClickItem = screenModel::showSourceDialog,
|
onLongClickItem = screenModel::showSourceDialog,
|
||||||
|
@ -1,32 +1,18 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import eu.kanade.domain.source.model.Source
|
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||||
import eu.kanade.presentation.browse.BrowseSourceScreen
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
import kotlinx.coroutines.channels.Channel
|
||||||
import eu.kanade.presentation.components.DuplicateMangaDialog
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import kotlinx.coroutines.launch
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Dialog
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
|
|
||||||
open class BrowseSourceController(bundle: Bundle) :
|
class BrowseSourceController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
||||||
FullComposeController<BrowseSourcePresenter>(bundle) {
|
|
||||||
|
|
||||||
constructor(sourceId: Long, query: String? = null) : this(
|
constructor(sourceId: Long, query: String? = null) : this(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@ -35,117 +21,27 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(source: CatalogueSource, query: String? = null) : this(source.id, query)
|
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
||||||
|
private val initialQuery = args.getString(SEARCH_QUERY_KEY)
|
||||||
|
|
||||||
constructor(source: Source, query: String? = null) : this(source.id, query)
|
private val queryEvent = Channel<BrowseSourceScreen.SearchType>()
|
||||||
|
|
||||||
/**
|
|
||||||
* Sheet containing filter items.
|
|
||||||
*/
|
|
||||||
protected var filterSheet: SourceFilterSheet? = null
|
|
||||||
|
|
||||||
override fun createPresenter(): BrowseSourcePresenter {
|
|
||||||
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
val scope = rememberCoroutineScope()
|
Navigator(screen = BrowseSourceScreen(sourceId = sourceId, query = initialQuery)) { navigator ->
|
||||||
val context = LocalContext.current
|
CurrentScreen()
|
||||||
val haptic = LocalHapticFeedback.current
|
|
||||||
|
|
||||||
BrowseSourceScreen(
|
LaunchedEffect(Unit) {
|
||||||
presenter = presenter,
|
queryEvent.consumeAsFlow()
|
||||||
navigateUp = ::navigateUp,
|
.collectLatest {
|
||||||
openFilterSheet = { filterSheet?.show() },
|
val screen = (navigator.lastItem as? BrowseSourceScreen)
|
||||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
when (it) {
|
||||||
onMangaLongClick = { manga ->
|
is BrowseSourceScreen.SearchType.Genre -> screen?.searchGenre(it.txt)
|
||||||
scope.launchIO {
|
is BrowseSourceScreen.SearchType.Text -> screen?.search(it.txt)
|
||||||
val duplicateManga = presenter.getDuplicateLibraryManga(manga)
|
|
||||||
when {
|
|
||||||
manga.favorite -> presenter.dialog = Dialog.RemoveManga(manga)
|
|
||||||
duplicateManga != null -> presenter.dialog = Dialog.AddDuplicateManga(manga, duplicateManga)
|
|
||||||
else -> presenter.addFavorite(manga)
|
|
||||||
}
|
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onWebViewClick = f@{
|
|
||||||
val source = presenter.source as? HttpSource ?: return@f
|
|
||||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
|
||||||
context.startActivity(intent)
|
|
||||||
},
|
|
||||||
incognitoMode = presenter.isIncognitoMode,
|
|
||||||
downloadedOnlyMode = presenter.isDownloadOnly,
|
|
||||||
)
|
|
||||||
|
|
||||||
val onDismissRequest = { presenter.dialog = null }
|
|
||||||
when (val dialog = presenter.dialog) {
|
|
||||||
null -> {}
|
|
||||||
is Dialog.Migrate -> {}
|
|
||||||
is Dialog.AddDuplicateManga -> {
|
|
||||||
DuplicateMangaDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onConfirm = { presenter.addFavorite(dialog.manga) },
|
|
||||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
|
||||||
duplicateFrom = presenter.getSourceOrStub(dialog.duplicate),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Dialog.RemoveManga -> {
|
|
||||||
RemoveMangaDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onConfirm = {
|
|
||||||
presenter.changeMangaFavorite(dialog.manga)
|
|
||||||
},
|
|
||||||
mangaToRemove = dialog.manga,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Dialog.ChangeMangaCategory -> {
|
|
||||||
ChangeCategoryDialog(
|
|
||||||
initialSelection = dialog.initialSelection,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onEditCategories = {
|
|
||||||
router.pushController(CategoryController())
|
|
||||||
},
|
|
||||||
onConfirm = { include, _ ->
|
|
||||||
presenter.changeMangaFavorite(dialog.manga)
|
|
||||||
presenter.moveMangaToCategories(dialog.manga, include)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(onBack = ::navigateUp)
|
|
||||||
|
|
||||||
LaunchedEffect(presenter.filters) {
|
|
||||||
initFilterSheet()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateUp() {
|
|
||||||
when {
|
|
||||||
!presenter.isUserQuery && presenter.searchQuery != null -> presenter.searchQuery = null
|
|
||||||
else -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun initFilterSheet() {
|
|
||||||
if (presenter.filters.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filterSheet = SourceFilterSheet(
|
|
||||||
activity!!,
|
|
||||||
onFilterClicked = {
|
|
||||||
presenter.search(filters = presenter.filters)
|
|
||||||
},
|
|
||||||
onResetClicked = {
|
|
||||||
presenter.reset()
|
|
||||||
filterSheet?.setFilters(presenter.filterItems)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
filterSheet?.setFilters(presenter.filterItems)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +50,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
* @param newQuery the new query.
|
* @param newQuery the new query.
|
||||||
*/
|
*/
|
||||||
fun searchWithQuery(newQuery: String) {
|
fun searchWithQuery(newQuery: String) {
|
||||||
presenter.search(newQuery)
|
viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Text(newQuery)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,46 +61,9 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
* @param genreName the name of the genre
|
* @param genreName the name of the genre
|
||||||
*/
|
*/
|
||||||
fun searchWithGenre(genreName: String) {
|
fun searchWithGenre(genreName: String) {
|
||||||
val defaultFilters = presenter.source!!.getFilterList()
|
viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Genre(genreName)) }
|
||||||
|
|
||||||
var genreExists = false
|
|
||||||
|
|
||||||
filter@ for (sourceFilter in defaultFilters) {
|
|
||||||
if (sourceFilter is Filter.Group<*>) {
|
|
||||||
for (filter in sourceFilter.state) {
|
|
||||||
if (filter is Filter<*> && filter.name.equals(genreName, true)) {
|
|
||||||
when (filter) {
|
|
||||||
is Filter.TriState -> filter.state = 1
|
|
||||||
is Filter.CheckBox -> filter.state = true
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
genreExists = true
|
|
||||||
break@filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (sourceFilter is Filter.Select<*>) {
|
|
||||||
val index = sourceFilter.values.filterIsInstance<String>()
|
|
||||||
.indexOfFirst { it.equals(genreName, true) }
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
sourceFilter.state = index
|
|
||||||
genreExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (genreExists) {
|
|
||||||
filterSheet?.setFilters(defaultFilters.toItems())
|
|
||||||
|
|
||||||
presenter.search(filters = defaultFilters)
|
|
||||||
} else {
|
|
||||||
searchWithQuery(genreName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected companion object {
|
private const val SOURCE_ID_KEY = "sourceId"
|
||||||
const val SOURCE_ID_KEY = "sourceId"
|
private const val SEARCH_QUERY_KEY = "searchQuery"
|
||||||
const val SEARCH_QUERY_KEY = "searchQuery"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,283 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Favorite
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.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
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||||
|
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||||
|
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
|
||||||
|
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
||||||
|
import eu.kanade.presentation.components.AppStateBanners
|
||||||
|
import eu.kanade.presentation.components.ChangeCategoryDialog
|
||||||
|
import eu.kanade.presentation.components.Divider
|
||||||
|
import eu.kanade.presentation.components.DuplicateMangaDialog
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.LocalRouter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.more.MoreController
|
||||||
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
|
||||||
|
data class BrowseSourceScreen(
|
||||||
|
private val sourceId: Long,
|
||||||
|
private val query: String? = null,
|
||||||
|
) : Screen {
|
||||||
|
|
||||||
|
override val key = uniqueScreenKey
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val router = LocalRouter.currentOrThrow
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
||||||
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
|
||||||
|
|
||||||
|
val onWebViewClick = f@{
|
||||||
|
val source = screenModel.source as? HttpSource ?: return@f
|
||||||
|
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
val navigateUp: () -> Unit = {
|
||||||
|
when {
|
||||||
|
navigator.canPop -> navigator.pop()
|
||||||
|
router.backstackSize > 1 -> router.popCurrentController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
|
BrowseSourceToolbar(
|
||||||
|
searchQuery = state.toolbarQuery,
|
||||||
|
onSearchQueryChange = screenModel::setToolbarQuery,
|
||||||
|
source = screenModel.source,
|
||||||
|
displayMode = screenModel.displayMode,
|
||||||
|
onDisplayModeChange = { screenModel.displayMode = it },
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onWebViewClick = onWebViewClick,
|
||||||
|
onHelpClick = onHelpClick,
|
||||||
|
onSearch = { screenModel.search(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = state.currentFilter == BrowseSourceScreenModel.Filter.Popular,
|
||||||
|
onClick = {
|
||||||
|
screenModel.reset()
|
||||||
|
screenModel.search(GetRemoteManga.QUERY_POPULAR)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Favorite,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(R.string.popular))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (screenModel.source.supportsLatest) {
|
||||||
|
FilterChip(
|
||||||
|
selected = state.currentFilter == BrowseSourceScreenModel.Filter.Latest,
|
||||||
|
onClick = {
|
||||||
|
screenModel.reset()
|
||||||
|
screenModel.search(GetRemoteManga.QUERY_LATEST)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.NewReleases,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(R.string.latest))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (state.filters.isNotEmpty()) {
|
||||||
|
FilterChip(
|
||||||
|
selected = state.currentFilter is BrowseSourceScreenModel.Filter.UserInput,
|
||||||
|
onClick = screenModel::openFilterSheet,
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.FilterList,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(R.string.action_filter))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
AppStateBanners(screenModel.isDownloadOnly, screenModel.isIncognitoMode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
|
) { paddingValues ->
|
||||||
|
val mangaList = remember(state.currentFilter) {
|
||||||
|
screenModel.getMangaListFlow(state.currentFilter)
|
||||||
|
}.collectAsLazyPagingItems()
|
||||||
|
|
||||||
|
BrowseSourceContent(
|
||||||
|
source = screenModel.source,
|
||||||
|
mangaList = mangaList,
|
||||||
|
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
|
displayMode = screenModel.displayMode,
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
onWebViewClick = onWebViewClick,
|
||||||
|
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
||||||
|
onLocalSourceHelpClick = onHelpClick,
|
||||||
|
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
||||||
|
onMangaLongClick = { manga ->
|
||||||
|
scope.launchIO {
|
||||||
|
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||||
|
when {
|
||||||
|
manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga))
|
||||||
|
duplicateManga != null -> screenModel.setDialog(
|
||||||
|
BrowseSourceScreenModel.Dialog.AddDuplicateManga(
|
||||||
|
manga,
|
||||||
|
duplicateManga,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else -> screenModel.addFavorite(manga)
|
||||||
|
}
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onDismissRequest = { screenModel.setDialog(null) }
|
||||||
|
when (val dialog = state.dialog) {
|
||||||
|
is BrowseSourceScreenModel.Dialog.Migrate -> {}
|
||||||
|
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
||||||
|
DuplicateMangaDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||||
|
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
||||||
|
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is BrowseSourceScreenModel.Dialog.RemoveManga -> {
|
||||||
|
RemoveMangaDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onConfirm = {
|
||||||
|
screenModel.changeMangaFavorite(dialog.manga)
|
||||||
|
},
|
||||||
|
mangaToRemove = dialog.manga,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is BrowseSourceScreenModel.Dialog.ChangeMangaCategory -> {
|
||||||
|
ChangeCategoryDialog(
|
||||||
|
initialSelection = dialog.initialSelection,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onEditCategories = {
|
||||||
|
router.pushController(CategoryController())
|
||||||
|
},
|
||||||
|
onConfirm = { include, _ ->
|
||||||
|
screenModel.changeMangaFavorite(dialog.manga)
|
||||||
|
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(onBack = navigateUp)
|
||||||
|
|
||||||
|
LaunchedEffect(state.filters) {
|
||||||
|
screenModel.initFilterSheet(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
queryEvent.receiveAsFlow()
|
||||||
|
.collectLatest {
|
||||||
|
when (it) {
|
||||||
|
is SearchType.Genre -> screenModel.searchGenre(it.txt)
|
||||||
|
is SearchType.Text -> screenModel.search(it.txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val queryEvent = Channel<SearchType>()
|
||||||
|
suspend fun search(query: String) = queryEvent.send(SearchType.Text(query))
|
||||||
|
suspend fun searchGenre(name: String) = queryEvent.send(SearchType.Genre(name))
|
||||||
|
|
||||||
|
sealed class SearchType(val txt: String) {
|
||||||
|
class Text(txt: String) : SearchType(txt)
|
||||||
|
class Genre(txt: String) : SearchType(txt)
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,22 @@
|
|||||||
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 android.os.Bundle
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import androidx.paging.map
|
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.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.core.prefs.CheckboxState
|
import eu.kanade.core.prefs.CheckboxState
|
||||||
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.core.prefs.mapAsCheckboxState
|
import eu.kanade.core.prefs.mapAsCheckboxState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
@ -39,8 +38,6 @@ import eu.kanade.domain.source.interactor.GetRemoteManga
|
|||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
import eu.kanade.presentation.browse.BrowseSourceState
|
|
||||||
import eu.kanade.presentation.browse.BrowseSourceStateImpl
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
@ -48,9 +45,7 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
|||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
|
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.CheckboxSectionItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
|
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
|
||||||
@ -70,19 +65,23 @@ import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||||
|
|
||||||
open class BrowseSourcePresenter(
|
class BrowseSourceScreenModel(
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
searchQuery: String? = null,
|
searchQuery: String?,
|
||||||
private val state: BrowseSourceStateImpl = BrowseSourceState(searchQuery) as BrowseSourceStateImpl,
|
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
preferences: BasePreferences = Injekt.get(),
|
preferences: BasePreferences = Injekt.get(),
|
||||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
@ -99,86 +98,122 @@ open class BrowseSourcePresenter(
|
|||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val insertTrack: InsertTrack = Injekt.get(),
|
private val insertTrack: InsertTrack = Injekt.get(),
|
||||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
|
||||||
) : BasePresenter<BrowseSourceController>(), BrowseSourceState by state {
|
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Filter.valueOf(searchQuery))) {
|
||||||
|
|
||||||
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
|
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
|
||||||
|
|
||||||
var displayMode by sourcePreferences.sourceDisplayMode().asState()
|
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
||||||
|
|
||||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
|
||||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
|
||||||
|
|
||||||
@Composable
|
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||||
fun getColumnsPreferenceForCurrentOrientation(): State<GridCells> {
|
|
||||||
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
/**
|
||||||
return produceState<GridCells>(initialValue = GridCells.Adaptive(128.dp), isLandscape) {
|
* Sheet containing filter items.
|
||||||
(if (isLandscape) libraryPreferences.landscapeColumns() else libraryPreferences.portraitColumns())
|
*/
|
||||||
.changes()
|
private var filterSheet: SourceFilterSheet? = null
|
||||||
.collectLatest { columns ->
|
|
||||||
value = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns)
|
init {
|
||||||
}
|
mutableState.update { it.copy(filters = source.getFilterList()) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
fun getColumnsPreference(orientation: Int): GridCells {
|
||||||
fun getMangaList(): Flow<PagingData<Manga>> {
|
val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
return remember(currentFilter) {
|
val columns = if (isLandscape) {
|
||||||
Pager(
|
libraryPreferences.landscapeColumns()
|
||||||
|
} else {
|
||||||
|
libraryPreferences.portraitColumns()
|
||||||
|
}.get()
|
||||||
|
return if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMangaListFlow(currentFilter: Filter): Flow<PagingData<StateFlow<Manga>>> {
|
||||||
|
return Pager(
|
||||||
PagingConfig(pageSize = 25),
|
PagingConfig(pageSize = 25),
|
||||||
) {
|
) {
|
||||||
getRemoteManga.subscribe(sourceId, currentFilter.query, currentFilter.filters)
|
getRemoteManga.subscribe(sourceId, currentFilter.query ?: "", currentFilter.filters)
|
||||||
}.flow
|
}.flow
|
||||||
.map {
|
.map { pagingData ->
|
||||||
it.map { sManga ->
|
pagingData.map { sManga ->
|
||||||
withIOContext {
|
val dbManga = withIOContext { networkToLocalManga.await(sManga.toDomainManga(sourceId)) }
|
||||||
networkToLocalManga.await(sManga.toDomainManga(sourceId))
|
getManga.subscribe(dbManga.url, dbManga.source)
|
||||||
}
|
.filterNotNull()
|
||||||
}
|
.onEach { initializeManga(it) }
|
||||||
}
|
.stateIn(coroutineScope)
|
||||||
.cachedIn(presenterScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun getManga(initialManga: Manga): State<Manga> {
|
|
||||||
return produceState(initialValue = initialManga) {
|
|
||||||
getManga.subscribe(initialManga.url, initialManga.source)
|
|
||||||
.collectLatest { manga ->
|
|
||||||
if (manga == null) return@collectLatest
|
|
||||||
withIOContext {
|
|
||||||
initializeManga(manga)
|
|
||||||
}
|
|
||||||
value = manga
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.cachedIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
val source = source ?: return
|
mutableState.update { it.copy(filters = source.getFilterList()) }
|
||||||
state.filters = source.getFilterList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String? = null, filters: FilterList? = null) {
|
fun search(query: String? = null, filters: FilterList? = null) {
|
||||||
Filter.valueOf(query ?: "").let {
|
Filter.valueOf(query).let {
|
||||||
if (it !is Filter.UserInput) {
|
if (it !is Filter.UserInput) {
|
||||||
state.currentFilter = it
|
mutableState.update { state -> state.copy(currentFilter = it) }
|
||||||
state.searchQuery = null
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val input: Filter.UserInput = if (currentFilter is Filter.UserInput) currentFilter as Filter.UserInput else Filter.UserInput()
|
val input = if (state.value.currentFilter is Filter.UserInput) {
|
||||||
state.currentFilter = input.copy(
|
state.value.currentFilter as Filter.UserInput
|
||||||
|
} else {
|
||||||
|
Filter.UserInput()
|
||||||
|
}
|
||||||
|
mutableState.update {
|
||||||
|
it.copy(
|
||||||
|
currentFilter = input.copy(
|
||||||
query = query ?: input.query,
|
query = query ?: input.query,
|
||||||
filters = filters ?: input.filters,
|
filters = filters ?: input.filters,
|
||||||
|
),
|
||||||
|
toolbarQuery = query ?: input.query,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
fun searchGenre(genreName: String) {
|
||||||
super.onCreate(savedState)
|
val defaultFilters = source.getFilterList()
|
||||||
|
var genreExists = false
|
||||||
|
|
||||||
state.source = sourceManager.get(sourceId) as? CatalogueSource ?: return
|
filter@ for (sourceFilter in defaultFilters) {
|
||||||
state.filters = source!!.getFilterList()
|
if (sourceFilter is SourceModelFilter.Group<*>) {
|
||||||
|
for (filter in sourceFilter.state) {
|
||||||
|
if (filter is SourceModelFilter<*> && filter.name.equals(genreName, true)) {
|
||||||
|
when (filter) {
|
||||||
|
is SourceModelFilter.TriState -> filter.state = 1
|
||||||
|
is SourceModelFilter.CheckBox -> filter.state = true
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
genreExists = true
|
||||||
|
break@filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sourceFilter is SourceModelFilter.Select<*>) {
|
||||||
|
val index = sourceFilter.values.filterIsInstance<String>()
|
||||||
|
.indexOfFirst { it.equals(genreName, true) }
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
sourceFilter.state = index
|
||||||
|
genreExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableState.update {
|
||||||
|
val filter = if (genreExists) {
|
||||||
|
Filter.UserInput(filters = defaultFilters)
|
||||||
|
} else {
|
||||||
|
Filter.UserInput(query = genreName)
|
||||||
|
}
|
||||||
|
it.copy(
|
||||||
|
filters = defaultFilters,
|
||||||
|
currentFilter = filter,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,7 +225,7 @@ open class BrowseSourcePresenter(
|
|||||||
if (manga.thumbnailUrl != null || manga.initialized) return
|
if (manga.thumbnailUrl != null || manga.initialized) return
|
||||||
withNonCancellableContext {
|
withNonCancellableContext {
|
||||||
try {
|
try {
|
||||||
val networkManga = source!!.getMangaDetails(manga.toSManga())
|
val networkManga = source.getMangaDetails(manga.toSManga())
|
||||||
val updatedManga = manga.copyFrom(networkManga)
|
val updatedManga = manga.copyFrom(networkManga)
|
||||||
.copy(initialized = true)
|
.copy(initialized = true)
|
||||||
|
|
||||||
@ -207,7 +242,7 @@ open class BrowseSourcePresenter(
|
|||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
*/
|
*/
|
||||||
fun changeMangaFavorite(manga: Manga) {
|
fun changeMangaFavorite(manga: Manga) {
|
||||||
presenterScope.launch {
|
coroutineScope.launch {
|
||||||
var new = manga.copy(
|
var new = manga.copy(
|
||||||
favorite = !manga.favorite,
|
favorite = !manga.favorite,
|
||||||
dateAdded = when (manga.favorite) {
|
dateAdded = when (manga.favorite) {
|
||||||
@ -233,7 +268,7 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(manga: Manga) {
|
fun addFavorite(manga: Manga) {
|
||||||
presenterScope.launch {
|
coroutineScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||||
@ -256,7 +291,7 @@ open class BrowseSourcePresenter(
|
|||||||
// Choose a category
|
// Choose a category
|
||||||
else -> {
|
else -> {
|
||||||
val preselectedIds = getCategories.await(manga.id).map { it.id }
|
val preselectedIds = getCategories.await(manga.id).map { it.id }
|
||||||
state.dialog = Dialog.ChangeMangaCategory(manga, categories.mapAsCheckboxState { it.id in preselectedIds })
|
setDialog(Dialog.ChangeMangaCategory(manga, categories.mapAsCheckboxState { it.id in preselectedIds }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +300,7 @@ open class BrowseSourcePresenter(
|
|||||||
private suspend fun autoAddTrack(manga: Manga) {
|
private suspend fun autoAddTrack(manga: Manga) {
|
||||||
loggedServices
|
loggedServices
|
||||||
.filterIsInstance<EnhancedTrackService>()
|
.filterIsInstance<EnhancedTrackService>()
|
||||||
.filter { it.accept(source!!) }
|
.filter { it.accept(source) }
|
||||||
.forEach { service ->
|
.forEach { service ->
|
||||||
try {
|
try {
|
||||||
service.match(manga.toDbManga())?.let { track ->
|
service.match(manga.toDbManga())?.let { track ->
|
||||||
@ -303,7 +338,7 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||||
presenterScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
setMangaCategories.await(
|
setMangaCategories.await(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
categoryIds = categoryIds.toList(),
|
categoryIds = categoryIds.toList(),
|
||||||
@ -311,13 +346,43 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Filter(open val query: String, open val filters: FilterList) {
|
fun openFilterSheet() {
|
||||||
|
filterSheet?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDialog(dialog: Dialog?) {
|
||||||
|
mutableState.update { it.copy(dialog = dialog) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setToolbarQuery(query: String?) {
|
||||||
|
mutableState.update { it.copy(toolbarQuery = query) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initFilterSheet(context: Context) {
|
||||||
|
val state = state.value
|
||||||
|
if (state.filters.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filterSheet = SourceFilterSheet(
|
||||||
|
context = context,
|
||||||
|
onFilterClicked = { search(filters = state.filters) },
|
||||||
|
onResetClicked = {
|
||||||
|
reset()
|
||||||
|
filterSheet?.setFilters(state.filterItems)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
filterSheet?.setFilters(state.filterItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Filter(open val query: String?, open val filters: FilterList) {
|
||||||
object Popular : Filter(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
object Popular : Filter(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
||||||
object Latest : Filter(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
object Latest : Filter(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
||||||
data class UserInput(override val query: String = "", override val filters: FilterList = FilterList()) : Filter(query = query, filters = filters)
|
data class UserInput(override val query: String? = null, override val filters: FilterList = FilterList()) : Filter(query = query, filters = filters)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun valueOf(query: String): Filter {
|
fun valueOf(query: String?): Filter {
|
||||||
return when (query) {
|
return when (query) {
|
||||||
GetRemoteManga.QUERY_POPULAR -> Popular
|
GetRemoteManga.QUERY_POPULAR -> Popular
|
||||||
GetRemoteManga.QUERY_LATEST -> Latest
|
GetRemoteManga.QUERY_LATEST -> Latest
|
||||||
@ -336,25 +401,40 @@ open class BrowseSourcePresenter(
|
|||||||
) : Dialog()
|
) : Dialog()
|
||||||
data class Migrate(val newManga: Manga) : Dialog()
|
data class Migrate(val newManga: Manga) : Dialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val currentFilter: Filter,
|
||||||
|
val filters: FilterList = FilterList(),
|
||||||
|
val toolbarQuery: String? = null,
|
||||||
|
val dialog: Dialog? = null,
|
||||||
|
) {
|
||||||
|
val filterItems = filters.toItems()
|
||||||
|
val isUserQuery = currentFilter is Filter.UserInput && !currentFilter.query.isNullOrEmpty()
|
||||||
|
val searchQuery = when (currentFilter) {
|
||||||
|
is Filter.UserInput -> currentFilter.query
|
||||||
|
Filter.Latest, Filter.Popular -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FilterList.toItems(): List<IFlexible<*>> {
|
private fun FilterList.toItems(): List<IFlexible<*>> {
|
||||||
return mapNotNull { filter ->
|
return mapNotNull { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is Filter.Header -> HeaderItem(filter)
|
is SourceModelFilter.Header -> HeaderItem(filter)
|
||||||
is Filter.Separator -> SeparatorItem(filter)
|
is SourceModelFilter.Separator -> SeparatorItem(filter)
|
||||||
is Filter.CheckBox -> CheckboxItem(filter)
|
is SourceModelFilter.CheckBox -> CheckboxItem(filter)
|
||||||
is Filter.TriState -> TriStateItem(filter)
|
is SourceModelFilter.TriState -> TriStateItem(filter)
|
||||||
is Filter.Text -> TextItem(filter)
|
is SourceModelFilter.Text -> TextItem(filter)
|
||||||
is Filter.Select<*> -> SelectItem(filter)
|
is SourceModelFilter.Select<*> -> SelectItem(filter)
|
||||||
is Filter.Group<*> -> {
|
is SourceModelFilter.Group<*> -> {
|
||||||
val group = GroupItem(filter)
|
val group = GroupItem(filter)
|
||||||
val subItems = filter.state.mapNotNull {
|
val subItems = filter.state.mapNotNull {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Filter.CheckBox -> CheckboxSectionItem(it)
|
is SourceModelFilter.CheckBox -> CheckboxSectionItem(it)
|
||||||
is Filter.TriState -> TriStateSectionItem(it)
|
is SourceModelFilter.TriState -> TriStateSectionItem(it)
|
||||||
is Filter.Text -> TextSectionItem(it)
|
is SourceModelFilter.Text -> TextSectionItem(it)
|
||||||
is Filter.Select<*> -> SelectSectionItem(it)
|
is SourceModelFilter.Select<*> -> SelectSectionItem(it)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,7 +442,7 @@ fun FilterList.toItems(): List<IFlexible<*>> {
|
|||||||
group.subItems = subItems
|
group.subItems = subItems
|
||||||
group
|
group
|
||||||
}
|
}
|
||||||
is Filter.Sort -> {
|
is SourceModelFilter.Sort -> {
|
||||||
val group = SortGroup(filter)
|
val group = SortGroup(filter)
|
||||||
val subItems = filter.values.map {
|
val subItems = filter.values.map {
|
||||||
SortItem(it, group)
|
SortItem(it, group)
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -13,12 +12,12 @@ import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
|||||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||||
|
|
||||||
class SourceFilterSheet(
|
class SourceFilterSheet(
|
||||||
activity: Activity,
|
context: Context,
|
||||||
private val onFilterClicked: () -> Unit,
|
private val onFilterClicked: () -> Unit,
|
||||||
private val onResetClicked: () -> Unit,
|
private val onResetClicked: () -> Unit,
|
||||||
) : BaseBottomSheetDialog(activity) {
|
) : BaseBottomSheetDialog(context) {
|
||||||
|
|
||||||
private var filterNavView: FilterNavigationView = FilterNavigationView(activity)
|
private var filterNavView: FilterNavigationView = FilterNavigationView(context)
|
||||||
|
|
||||||
override fun createView(inflater: LayoutInflater): View {
|
override fun createView(inflater: LayoutInflater): View {
|
||||||
filterNavView.onFilterClicked = {
|
filterNavView.onFilterClicked = {
|
||||||
|
@ -44,7 +44,7 @@ class GlobalSearchScreen(
|
|||||||
if (!screenModel.incognitoMode.get()) {
|
if (!screenModel.incognitoMode.get()) {
|
||||||
screenModel.lastUsedSourceId.set(it.id)
|
screenModel.lastUsedSourceId.set(it.id)
|
||||||
}
|
}
|
||||||
router.pushController(BrowseSourceController(it, state.searchQuery))
|
router.pushController(BrowseSourceController(it.id, state.searchQuery))
|
||||||
},
|
},
|
||||||
onClickItem = { router.pushController(MangaController(it.id, true)) },
|
onClickItem = { router.pushController(MangaController(it.id, true)) },
|
||||||
onLongClickItem = { router.pushController(MangaController(it.id, true)) },
|
onLongClickItem = { router.pushController(MangaController(it.id, true)) },
|
||||||
|
Loading…
Reference in New Issue
Block a user