Initial conversion of browse tabs to full Compose
TODO: - Global search should launch a controller with the search textfield focused. This is pending a Compose rewrite of that screen. - Better migrate sort UI - Extensions search (cherry picked from commit 92e83f702c775d5ab5a0b4248995308155150811) # Conflicts: # app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt # app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesPresenter.kt
This commit is contained in:
parent
aaa2a961ae
commit
8c182df784
@ -6,10 +6,9 @@ class SetMigrateSorting(
|
|||||||
private val preferences: PreferencesHelper,
|
private val preferences: PreferencesHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun await(mode: Mode, isAscending: Boolean) {
|
fun await(mode: Mode, direction: Direction) {
|
||||||
val direction = if (isAscending) Direction.ASCENDING else Direction.DESCENDING
|
|
||||||
preferences.migrationSortingDirection().set(direction)
|
|
||||||
preferences.migrationSortingMode().set(mode)
|
preferences.migrationSortingMode().set(mode)
|
||||||
|
preferences.migrationSortingDirection().set(direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.components.TabIndicator
|
||||||
|
import eu.kanade.presentation.components.TabText
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseScreen(
|
||||||
|
startIndex: Int? = null,
|
||||||
|
tabs: List<BrowseTab>,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val state = rememberPagerState()
|
||||||
|
|
||||||
|
LaunchedEffect(startIndex) {
|
||||||
|
if (startIndex != null) {
|
||||||
|
state.scrollToPage(startIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(R.string.browse),
|
||||||
|
actions = {
|
||||||
|
AppBarActions(tabs[state.currentPage].actions)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
TabRow(
|
||||||
|
selectedTabIndex = state.currentPage,
|
||||||
|
indicator = { TabIndicator(it[state.currentPage]) },
|
||||||
|
) {
|
||||||
|
tabs.forEachIndexed { index, tab ->
|
||||||
|
Tab(
|
||||||
|
selected = state.currentPage == index,
|
||||||
|
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
||||||
|
text = {
|
||||||
|
TabText(stringResource(tab.titleRes), tab.badgeNumber, state.currentPage == index)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalPager(
|
||||||
|
count = tabs.size,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = state,
|
||||||
|
verticalAlignment = Alignment.Top,
|
||||||
|
) { page ->
|
||||||
|
tabs[page].content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BrowseTab(
|
||||||
|
@StringRes val titleRes: Int,
|
||||||
|
val badgeNumber: Int? = null,
|
||||||
|
val actions: List<AppBar.Action> = emptyList(),
|
||||||
|
val content: @Composable () -> Unit,
|
||||||
|
)
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseTabWrapper(tab: BrowseTab) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(tab.titleRes),
|
||||||
|
actions = {
|
||||||
|
AppBarActions(tab.actions)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
tab.content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,15 +23,12 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@ -60,7 +57,6 @@ import exh.source.anyIs
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionScreen(
|
fun ExtensionScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
presenter: ExtensionsPresenter,
|
presenter: ExtensionsPresenter,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
@ -71,10 +67,8 @@ fun ExtensionScreen(
|
|||||||
onOpenExtension: (Extension.Installed) -> Unit,
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onLaunched: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
|
||||||
state = rememberSwipeRefreshState(presenter.isRefreshing),
|
state = rememberSwipeRefreshState(presenter.isRefreshing),
|
||||||
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
|
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
@ -93,7 +87,6 @@ fun ExtensionScreen(
|
|||||||
onTrustExtension = onTrustExtension,
|
onTrustExtension = onTrustExtension,
|
||||||
onOpenExtension = onOpenExtension,
|
onOpenExtension = onOpenExtension,
|
||||||
onClickUpdateAll = onClickUpdateAll,
|
onClickUpdateAll = onClickUpdateAll,
|
||||||
onLaunched = onLaunched,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +104,6 @@ fun ExtensionContent(
|
|||||||
onTrustExtension: (Extension.Untrusted) -> Unit,
|
onTrustExtension: (Extension.Untrusted) -> Unit,
|
||||||
onOpenExtension: (Extension.Installed) -> Unit,
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
onLaunched: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||||
|
|
||||||
@ -190,9 +182,6 @@ fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
onLaunched()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ interface ExtensionsState {
|
|||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
val isRefreshing: Boolean
|
val isRefreshing: Boolean
|
||||||
val items: List<ExtensionUiModel>
|
val items: List<ExtensionUiModel>
|
||||||
|
val updates: Int
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,5 +22,6 @@ class ExtensionsStateImpl : ExtensionsState {
|
|||||||
override var isLoading: Boolean by mutableStateOf(true)
|
override var isLoading: Boolean by mutableStateOf(true)
|
||||||
override var isRefreshing: Boolean by mutableStateOf(false)
|
override var isRefreshing: Boolean by mutableStateOf(false)
|
||||||
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
|
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
|
||||||
|
override var updates: Int by mutableStateOf(0)
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,29 +7,39 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
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.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -62,11 +72,13 @@ data class FeedItemUI(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedScreen(
|
fun FeedScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
presenter: FeedPresenter,
|
presenter: FeedPresenter,
|
||||||
|
onClickAdd: (CatalogueSource) -> Unit,
|
||||||
|
onClickCreate: (CatalogueSource, SavedSearch?) -> Unit,
|
||||||
onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit,
|
onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickDelete: (FeedSavedSearch) -> Unit,
|
onClickDelete: (FeedSavedSearch) -> Unit,
|
||||||
|
onClickDeleteConfirm: (FeedSavedSearch) -> Unit,
|
||||||
onClickManga: (Manga) -> Unit,
|
onClickManga: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
@ -74,11 +86,13 @@ fun FeedScreen(
|
|||||||
presenter.isEmpty -> EmptyScreen(R.string.feed_tab_empty)
|
presenter.isEmpty -> EmptyScreen(R.string.feed_tab_empty)
|
||||||
else -> {
|
else -> {
|
||||||
FeedList(
|
FeedList(
|
||||||
nestedScrollConnection = nestedScrollInterop,
|
|
||||||
state = presenter,
|
state = presenter,
|
||||||
|
onClickAdd = onClickAdd,
|
||||||
|
onClickCreate = onClickCreate,
|
||||||
onClickSavedSearch = onClickSavedSearch,
|
onClickSavedSearch = onClickSavedSearch,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
|
onClickDeleteConfirm = onClickDeleteConfirm,
|
||||||
onClickManga = onClickManga,
|
onClickManga = onClickManga,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -87,15 +101,16 @@ fun FeedScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedList(
|
fun FeedList(
|
||||||
nestedScrollConnection: NestedScrollConnection,
|
|
||||||
state: FeedState,
|
state: FeedState,
|
||||||
|
onClickAdd: (CatalogueSource) -> Unit,
|
||||||
|
onClickCreate: (CatalogueSource, SavedSearch?) -> Unit,
|
||||||
onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit,
|
onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickDelete: (FeedSavedSearch) -> Unit,
|
onClickDelete: (FeedSavedSearch) -> Unit,
|
||||||
|
onClickDeleteConfirm: (FeedSavedSearch) -> Unit,
|
||||||
onClickManga: (Manga) -> Unit,
|
onClickManga: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
|
||||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
@ -112,6 +127,41 @@ fun FeedList(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (val dialog = state.dialog) {
|
||||||
|
is FeedPresenter.Dialog.AddFeed -> {
|
||||||
|
FeedAddDialog(
|
||||||
|
sources = dialog.options,
|
||||||
|
onDismiss = { state.dialog = null },
|
||||||
|
onClickAdd = {
|
||||||
|
state.dialog = null
|
||||||
|
onClickAdd(it ?: return@FeedAddDialog)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is FeedPresenter.Dialog.AddFeedSearch -> {
|
||||||
|
FeedAddSearchDialog(
|
||||||
|
source = dialog.source,
|
||||||
|
savedSearches = dialog.options,
|
||||||
|
onDismiss = { state.dialog = null },
|
||||||
|
onClickAdd = { source, savedSearch ->
|
||||||
|
state.dialog = null
|
||||||
|
onClickCreate(source, savedSearch)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is FeedPresenter.Dialog.DeleteFeed -> {
|
||||||
|
FeedDeleteConfirmDialog(
|
||||||
|
feed = dialog.feed,
|
||||||
|
onDismiss = { state.dialog = null },
|
||||||
|
onClickDeleteConfirm = {
|
||||||
|
state.dialog = null
|
||||||
|
onClickDeleteConfirm(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
null -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -207,7 +257,8 @@ fun FeedCardItem(
|
|||||||
.aspectRatio(MangaCover.Book.ratio),
|
.aspectRatio(MangaCover.Book.ratio),
|
||||||
) {
|
) {
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.alpha(
|
.alpha(
|
||||||
if (manga.favorite) 0.3f else 1.0f,
|
if (manga.favorite) 0.3f else 1.0f,
|
||||||
),
|
),
|
||||||
@ -239,3 +290,110 @@ fun FeedCardItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedAddDialog(
|
||||||
|
sources: List<CatalogueSource>,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onClickAdd: (CatalogueSource?) -> Unit,
|
||||||
|
) {
|
||||||
|
var selected by remember { mutableStateOf<Int?>(null) }
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.feed))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
RadioSelector(options = sources, selected = selected) {
|
||||||
|
selected = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onClickAdd(selected?.let { sources[it] }) }) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedAddSearchDialog(
|
||||||
|
source: CatalogueSource,
|
||||||
|
savedSearches: List<SavedSearch?>,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onClickAdd: (CatalogueSource, SavedSearch?) -> Unit,
|
||||||
|
) {
|
||||||
|
var selected by remember { mutableStateOf<Int?>(null) }
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(text = source.name)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val savedSearchStrings = remember {
|
||||||
|
savedSearches.map {
|
||||||
|
it?.name ?: context.getString(R.string.latest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RadioSelector(
|
||||||
|
options = savedSearches,
|
||||||
|
optionStrings = savedSearchStrings,
|
||||||
|
selected = selected,
|
||||||
|
) {
|
||||||
|
selected = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onClickAdd(source, selected?.let { savedSearches[it] }) }) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> RadioSelector(
|
||||||
|
options: List<T>,
|
||||||
|
optionStrings: List<String> = remember { options.map { it.toString() } },
|
||||||
|
selected: Int?,
|
||||||
|
onSelectOption: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||||
|
optionStrings.forEachIndexed { index, option ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(48.dp)
|
||||||
|
.clickable { onSelectOption(index) },
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
RadioButton(selected == index, onClick = null)
|
||||||
|
Spacer(Modifier.width(4.dp))
|
||||||
|
Text(option, maxLines = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedDeleteConfirmDialog(
|
||||||
|
feed: FeedSavedSearch,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onClickDeleteConfirm: (FeedSavedSearch) -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.feed))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.feed_delete))
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onClickDeleteConfirm(feed) }) {
|
||||||
|
Text(text = stringResource(R.string.action_delete))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.feed.FeedPresenter
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface FeedState {
|
interface FeedState {
|
||||||
|
var dialog: FeedPresenter.Dialog?
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
val items: List<FeedItemUI>?
|
val items: List<FeedItemUI>?
|
||||||
@ -17,6 +19,7 @@ fun FeedState(): FeedState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FeedStateImpl : FeedState {
|
class FeedStateImpl : FeedState {
|
||||||
|
override var dialog: FeedPresenter.Dialog? by mutableStateOf(null)
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
override var isLoading: Boolean by mutableStateOf(true)
|
||||||
override var isEmpty: Boolean by mutableStateOf(false)
|
override var isEmpty: Boolean by mutableStateOf(false)
|
||||||
override var items: List<FeedItemUI>? by mutableStateOf(null)
|
override var items: List<FeedItemUI>? by mutableStateOf(null)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.asPaddingValues
|
|||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -15,12 +16,11 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
@ -41,7 +41,6 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
presenter: MigrationSourcesPresenter,
|
presenter: MigrationSourcesPresenter,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -54,13 +53,16 @@ fun MigrateSourceScreen(
|
|||||||
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
|
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
|
||||||
else ->
|
else ->
|
||||||
MigrateSourceList(
|
MigrateSourceList(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
list = presenter.items,
|
list = presenter.items,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = { source ->
|
onLongClickItem = { source ->
|
||||||
val sourceId = source.id.toString()
|
val sourceId = source.id.toString()
|
||||||
context.copyToClipboard(sourceId, sourceId)
|
context.copyToClipboard(sourceId, sourceId)
|
||||||
},
|
},
|
||||||
|
sortingMode = presenter.sortingMode,
|
||||||
|
onToggleSortingMode = { presenter.toggleSortingMode() },
|
||||||
|
sortingDirection = presenter.sortingDirection,
|
||||||
|
onToggleSortingDirection = { presenter.toggleSortingDirection() },
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickAll = onClickAll,
|
onClickAll = onClickAll,
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -70,18 +72,31 @@ fun MigrateSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceList(
|
fun MigrateSourceList(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
list: List<Pair<Source, Long>>,
|
list: List<Pair<Source, Long>>,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
sortingMode: SetMigrateSorting.Mode,
|
||||||
|
onToggleSortingMode: () -> Unit,
|
||||||
|
sortingDirection: SetMigrateSorting.Direction,
|
||||||
|
onToggleSortingDirection: () -> Unit,
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickAll: (Source) -> Unit,
|
onClickAll: (Source) -> Unit,
|
||||||
// SY <--
|
// SY <--
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
|
||||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
|
stickyHeader {
|
||||||
|
Row {
|
||||||
|
Button(onClick = onToggleSortingMode) {
|
||||||
|
Text(sortingMode.toString())
|
||||||
|
}
|
||||||
|
Button(onClick = onToggleSortingDirection) {
|
||||||
|
Text(sortingDirection.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item(key = "title") {
|
item(key = "title") {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.migration_selection_prompt),
|
text = stringResource(R.string.migration_selection_prompt),
|
||||||
|
|||||||
@ -4,12 +4,15 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
|
||||||
interface MigrateSourceState {
|
interface MigrateSourceState {
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
val items: List<Pair<Source, Long>>
|
val items: List<Pair<Source, Long>>
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
|
val sortingMode: SetMigrateSorting.Mode
|
||||||
|
val sortingDirection: SetMigrateSorting.Direction
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MigrateSourceState(): MigrateSourceState {
|
fun MigrateSourceState(): MigrateSourceState {
|
||||||
@ -20,4 +23,6 @@ class MigrateSourceStateImpl : MigrateSourceState {
|
|||||||
override var isLoading: Boolean by mutableStateOf(true)
|
override var isLoading: Boolean by mutableStateOf(true)
|
||||||
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
|
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||||
|
override var sortingMode: SetMigrateSorting.Mode by mutableStateOf(SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
override var sortingDirection: SetMigrateSorting.Direction by mutableStateOf(SetMigrateSorting.Direction.ASCENDING)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -51,7 +49,6 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(
|
fun SourcesScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
|
||||||
presenter: SourcesPresenter,
|
presenter: SourcesPresenter,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onClickDisable: (Source) -> Unit,
|
onClickDisable: (Source) -> Unit,
|
||||||
@ -66,7 +63,6 @@ fun SourcesScreen(
|
|||||||
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
|
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
|
||||||
else -> {
|
else -> {
|
||||||
SourceList(
|
SourceList(
|
||||||
nestedScrollConnection = nestedScrollInterop,
|
|
||||||
state = presenter,
|
state = presenter,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onClickDisable = onClickDisable,
|
onClickDisable = onClickDisable,
|
||||||
@ -90,7 +86,6 @@ fun SourcesScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceList(
|
fun SourceList(
|
||||||
nestedScrollConnection: NestedScrollConnection,
|
|
||||||
state: SourcesState,
|
state: SourcesState,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onClickDisable: (Source) -> Unit,
|
onClickDisable: (Source) -> Unit,
|
||||||
@ -100,7 +95,6 @@ fun SourceList(
|
|||||||
onClickToggleDataSaver: (Source) -> Unit,
|
onClickToggleDataSaver: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
|
||||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
|
|||||||
50
app/src/main/java/eu/kanade/presentation/components/Tabs.kt
Normal file
50
app/src/main/java/eu/kanade/presentation/components/Tabs.kt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.TabPosition
|
||||||
|
import androidx.compose.material3.TabRowDefaults
|
||||||
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TabIndicator(currentTabPosition: TabPosition) {
|
||||||
|
TabRowDefaults.Indicator(
|
||||||
|
Modifier
|
||||||
|
.tabIndicatorOffset(currentTabPosition)
|
||||||
|
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TabText(
|
||||||
|
text: String,
|
||||||
|
badgeCount: Int? = null,
|
||||||
|
isCurrentPage: Boolean,
|
||||||
|
) {
|
||||||
|
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = if (isCurrentPage) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
if (badgeCount != null) {
|
||||||
|
Pill(
|
||||||
|
text = "$badgeCount",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha),
|
||||||
|
fontSize = 10.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,31 +2,22 @@ package eu.kanade.presentation.library.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRowDefaults
|
|
||||||
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
|
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
|
||||||
import eu.kanade.presentation.components.IncognitoModeBanner
|
import eu.kanade.presentation.components.IncognitoModeBanner
|
||||||
import eu.kanade.presentation.components.Pill
|
import eu.kanade.presentation.components.TabIndicator
|
||||||
|
import eu.kanade.presentation.components.TabText
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -49,13 +40,7 @@ fun LibraryTabs(
|
|||||||
ScrollableTabRow(
|
ScrollableTabRow(
|
||||||
selectedTabIndex = state.currentPage.coerceAtMost(categories.lastIndex),
|
selectedTabIndex = state.currentPage.coerceAtMost(categories.lastIndex),
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
indicator = { tabPositions ->
|
indicator = { TabIndicator(it[state.currentPage.coerceAtMost(categories.lastIndex)]) },
|
||||||
TabRowDefaults.Indicator(
|
|
||||||
Modifier
|
|
||||||
.tabIndicatorOffset(tabPositions[state.currentPage.coerceAtMost(categories.lastIndex)])
|
|
||||||
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
categories.forEachIndexed { index, category ->
|
categories.forEachIndexed { index, category ->
|
||||||
val count by if (showMangaCount) {
|
val count by if (showMangaCount) {
|
||||||
@ -67,23 +52,13 @@ fun LibraryTabs(
|
|||||||
selected = state.currentPage == index,
|
selected = state.currentPage == index,
|
||||||
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
||||||
text = {
|
text = {
|
||||||
Row(
|
TabText(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
// SY -->
|
||||||
) {
|
text = getCategoryName(category, category.visualName),
|
||||||
Text(
|
// SY <--
|
||||||
// SY -->
|
count,
|
||||||
text = getCategoryName(category, category.visualName),
|
state.currentPage == index,
|
||||||
// SY <--
|
)
|
||||||
color = if (state.currentPage == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
|
|
||||||
)
|
|
||||||
if (count != null) {
|
|
||||||
Pill(
|
|
||||||
text = "$count",
|
|
||||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha),
|
|
||||||
fontSize = 10.sp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,7 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
|
||||||
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
|
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
import nucleus.presenter.Presenter
|
import nucleus.presenter.Presenter
|
||||||
|
|
||||||
@ -29,33 +26,11 @@ abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compose controller with a Nucleus presenter.
|
|
||||||
*/
|
|
||||||
abstract class ComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
|
|
||||||
NucleusController<ComposeControllerBinding, P>(bundle),
|
|
||||||
ComposeContentController {
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) =
|
|
||||||
ComposeControllerBinding.inflate(inflater)
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.root.apply {
|
|
||||||
setComposeContent {
|
|
||||||
val nestedScrollInterop = rememberNestedScrollInteropConnection()
|
|
||||||
ComposeContent(nestedScrollInterop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic Compose controller without a presenter.
|
* Basic Compose controller without a presenter.
|
||||||
*/
|
*/
|
||||||
abstract class BasicFullComposeController :
|
abstract class BasicFullComposeController(bundle: Bundle? = null) :
|
||||||
BaseController<ComposeControllerBinding>(),
|
BaseController<ComposeControllerBinding>(bundle),
|
||||||
FullComposeContentController {
|
FullComposeContentController {
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) =
|
override fun createBinding(inflater: LayoutInflater) =
|
||||||
@ -72,29 +47,6 @@ abstract class BasicFullComposeController :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SearchableComposeController<P : BasePresenter<*>>(bundle: Bundle? = null) :
|
|
||||||
SearchableNucleusController<ComposeControllerBinding, P>(bundle),
|
|
||||||
ComposeContentController {
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) =
|
|
||||||
ComposeControllerBinding.inflate(inflater)
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.root.apply {
|
|
||||||
setComposeContent {
|
|
||||||
val nestedScrollInterop = rememberNestedScrollInteropConnection()
|
|
||||||
ComposeContent(nestedScrollInterop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FullComposeContentController {
|
interface FullComposeContentController {
|
||||||
@Composable fun ComposeContent()
|
@Composable fun ComposeContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComposeContentController {
|
|
||||||
@Composable fun ComposeContent(nestedScrollInterop: NestedScrollConnection)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
|
|
||||||
interface TabbedController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true to let activity updates tabs visibility (to visible)
|
|
||||||
*/
|
|
||||||
fun configureTabs(tabs: TabLayout): Boolean = true
|
|
||||||
|
|
||||||
fun cleanupTabs(tabs: TabLayout) {}
|
|
||||||
}
|
|
||||||
@ -1,174 +1,63 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import com.bluelinelabs.conductor.Controller
|
import eu.kanade.presentation.browse.BrowseScreen
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
|
||||||
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter
|
|
||||||
import com.google.android.material.badge.BadgeDrawable
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
import com.jakewharton.rxrelay.PublishRelay
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.databinding.PagerControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RxController
|
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsController
|
import eu.kanade.tachiyomi.ui.browse.feed.feedTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.feed.FeedController
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourcesTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
|
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class BrowseController :
|
class BrowseController : FullComposeController<BrowsePresenter>, RootController {
|
||||||
RxController<PagerControllerBinding>,
|
|
||||||
RootController,
|
@Suppress("unused")
|
||||||
TabbedController {
|
constructor(bundle: Bundle? = null) : this(bundle?.getBoolean(TO_EXTENSIONS_EXTRA) ?: false)
|
||||||
|
|
||||||
constructor(toExtensions: Boolean = false) : super(
|
constructor(toExtensions: Boolean = false) : super(
|
||||||
bundleOf(TO_EXTENSIONS_EXTRA to toExtensions),
|
bundleOf(TO_EXTENSIONS_EXTRA to toExtensions),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle) : this(bundle.getBoolean(TO_EXTENSIONS_EXTRA))
|
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
private val toExtensions = args.getBoolean(TO_EXTENSIONS_EXTRA, false)
|
private val toExtensions = args.getBoolean(TO_EXTENSIONS_EXTRA, false)
|
||||||
|
|
||||||
val extensionListUpdateRelay: PublishRelay<Boolean> = PublishRelay.create()
|
override fun createPresenter() = BrowsePresenter()
|
||||||
|
|
||||||
private var adapter: BrowseAdapter? = null
|
@Composable
|
||||||
|
override fun ComposeContent() {
|
||||||
|
BrowseScreen(
|
||||||
|
// SY -->
|
||||||
|
startIndex = 2.takeIf { toExtensions },
|
||||||
|
tabs = (
|
||||||
|
if (presenter.feedTabInFront) listOf(
|
||||||
|
feedTab(router, presenter.feedPresenter),
|
||||||
|
sourcesTab(router, presenter.sourcesPresenter),
|
||||||
|
) else listOf(
|
||||||
|
sourcesTab(router, presenter.sourcesPresenter),
|
||||||
|
feedTab(router, presenter.feedPresenter),
|
||||||
|
)
|
||||||
|
) + listOf(
|
||||||
|
extensionsTab(router, presenter.extensionsPresenter),
|
||||||
|
migrateSourcesTab(router, presenter.migrationSourcesPresenter),
|
||||||
|
),
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
LaunchedEffect(Unit) {
|
||||||
return resources!!.getString(R.string.browse)
|
(activity as? MainActivity)?.ready = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
requestPermissionsSafe(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 301)
|
||||||
adapter = BrowseAdapter()
|
|
||||||
binding.pager.adapter = adapter
|
|
||||||
|
|
||||||
if (toExtensions) {
|
|
||||||
binding.pager.currentItem = EXTENSIONS_CONTROLLER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
adapter = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
if (type.isEnter) {
|
|
||||||
(activity as? MainActivity)?.binding?.tabs?.apply {
|
|
||||||
setupWithViewPager(binding.pager)
|
|
||||||
|
|
||||||
// Show badge on tab for extension updates
|
|
||||||
setExtensionUpdateBadge()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureTabs(tabs: TabLayout): Boolean {
|
|
||||||
with(tabs) {
|
|
||||||
tabGravity = TabLayout.GRAVITY_FILL
|
|
||||||
tabMode = TabLayout.MODE_FIXED
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanupTabs(tabs: TabLayout) {
|
|
||||||
// Remove extension update badge
|
|
||||||
tabs.getTabAt(EXTENSIONS_CONTROLLER)?.removeBadge()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setExtensionUpdateBadge() {
|
|
||||||
/* It's possible to switch to the Library controller by the time setExtensionUpdateBadge
|
|
||||||
is called, resulting in a badge being put on the category tabs (if enabled).
|
|
||||||
This check prevents that from happening */
|
|
||||||
if (router.backstack.lastOrNull()?.controller !is BrowseController) return
|
|
||||||
|
|
||||||
(activity as? MainActivity)?.binding?.tabs?.apply {
|
|
||||||
val updates = preferences.extensionUpdatesCount().get()
|
|
||||||
if (updates > 0) {
|
|
||||||
// SY -->
|
|
||||||
val badge: BadgeDrawable? = getTabAt(EXTENSIONS_CONTROLLER)?.orCreateBadge
|
|
||||||
// SY <--
|
|
||||||
badge?.isVisible = true
|
|
||||||
} else {
|
|
||||||
getTabAt(EXTENSIONS_CONTROLLER)?.removeBadge()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class BrowseAdapter : RouterPagerAdapter(this@BrowseController) {
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private val tabTitles = (
|
|
||||||
if (preferences.feedTabInFront().get()) {
|
|
||||||
listOf(
|
|
||||||
R.string.feed,
|
|
||||||
R.string.label_sources,
|
|
||||||
R.string.label_extensions,
|
|
||||||
R.string.label_migration,
|
|
||||||
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
listOf(
|
|
||||||
R.string.label_sources,
|
|
||||||
R.string.feed,
|
|
||||||
R.string.label_extensions,
|
|
||||||
R.string.label_migration,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// SY <--
|
|
||||||
.map { resources!!.getString(it) }
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
|
||||||
return tabTitles.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureRouter(router: Router, position: Int) {
|
|
||||||
if (!router.hasRootController()) {
|
|
||||||
val controller: Controller = when (position) {
|
|
||||||
// SY -->
|
|
||||||
SOURCES_CONTROLLER -> if (preferences.feedTabInFront().get()) FeedController() else SourcesController()
|
|
||||||
FEED_CONTROLLER -> if (!preferences.feedTabInFront().get()) FeedController() else SourcesController()
|
|
||||||
// SY <--
|
|
||||||
EXTENSIONS_CONTROLLER -> ExtensionsController()
|
|
||||||
MIGRATION_CONTROLLER -> MigrationSourcesController()
|
|
||||||
else -> error("Wrong position $position")
|
|
||||||
}
|
|
||||||
router.setRoot(RouterTransaction.with(controller))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence {
|
|
||||||
return tabTitles[position]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TO_EXTENSIONS_EXTRA = "to_extensions"
|
|
||||||
|
|
||||||
const val SOURCES_CONTROLLER = 0
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
const val FEED_CONTROLLER = 1
|
|
||||||
const val EXTENSIONS_CONTROLLER = 2
|
|
||||||
const val MIGRATION_CONTROLLER = 3
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val TO_EXTENSIONS_EXTRA = "to_extensions"
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.feed.FeedPresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class BrowsePresenter(
|
||||||
|
// SY -->
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
|
// SY <--
|
||||||
|
) : BasePresenter<BrowseController>() {
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
val feedTabInFront = preferences.feedTabInFront().get()
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
val sourcesPresenter = SourcesPresenter(presenterScope, controllerMode = SourcesController.Mode.CATALOGUE, smartSearchConfig = null)
|
||||||
|
val feedPresenter = FeedPresenter(presenterScope)
|
||||||
|
|
||||||
|
// SY <--
|
||||||
|
val extensionsPresenter = ExtensionsPresenter(presenterScope)
|
||||||
|
val migrationSourcesPresenter = MigrationSourcesPresenter(presenterScope)
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
sourcesPresenter.onCreate()
|
||||||
|
// SY -->
|
||||||
|
feedPresenter.onCreate()
|
||||||
|
// SY <--
|
||||||
|
extensionsPresenter.onCreate()
|
||||||
|
migrationSourcesPresenter.onCreate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
feedPresenter.onDestroy()
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
}
|
||||||
@ -1,120 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension
|
|
||||||
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import androidx.appcompat.widget.SearchView
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import eu.kanade.presentation.browse.ExtensionScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
|
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import reactivecircus.flowbinding.appcompat.queryTextChanges
|
|
||||||
|
|
||||||
class ExtensionsController : ComposeController<ExtensionsPresenter>() {
|
|
||||||
|
|
||||||
private var query = ""
|
|
||||||
|
|
||||||
init {
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle() = applicationContext?.getString(R.string.label_extensions)
|
|
||||||
|
|
||||||
override fun createPresenter() = ExtensionsPresenter()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
|
||||||
ExtensionScreen(
|
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
presenter = presenter,
|
|
||||||
onLongClickItem = { extension ->
|
|
||||||
when (extension) {
|
|
||||||
is Extension.Available -> presenter.installExtension(extension)
|
|
||||||
else -> presenter.uninstallExtension(extension.pkgName)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClickItemCancel = { extension ->
|
|
||||||
presenter.cancelInstallUpdateExtension(extension)
|
|
||||||
},
|
|
||||||
onClickUpdateAll = {
|
|
||||||
presenter.updateAllExtensions()
|
|
||||||
},
|
|
||||||
onLaunched = {
|
|
||||||
val ctrl = parentController as BrowseController
|
|
||||||
ctrl.setExtensionUpdateBadge()
|
|
||||||
ctrl.extensionListUpdateRelay.call(true)
|
|
||||||
},
|
|
||||||
onInstallExtension = {
|
|
||||||
presenter.installExtension(it)
|
|
||||||
},
|
|
||||||
onOpenExtension = {
|
|
||||||
val controller = ExtensionDetailsController(it.pkgName)
|
|
||||||
parentController!!.router.pushController(controller)
|
|
||||||
},
|
|
||||||
onTrustExtension = {
|
|
||||||
presenter.trustSignature(it.signatureHash)
|
|
||||||
},
|
|
||||||
onUninstallExtension = {
|
|
||||||
presenter.uninstallExtension(it.pkgName)
|
|
||||||
},
|
|
||||||
onUpdateExtension = {
|
|
||||||
presenter.updateExtension(it)
|
|
||||||
},
|
|
||||||
onRefresh = {
|
|
||||||
presenter.findAvailableExtensions()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_search -> expandActionViewFromInteraction = true
|
|
||||||
R.id.action_settings -> {
|
|
||||||
parentController!!.router.pushController(ExtensionFilterController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
if (type.isPush) {
|
|
||||||
presenter.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.browse_extensions, menu)
|
|
||||||
|
|
||||||
val searchItem = menu.findItem(R.id.action_search)
|
|
||||||
val searchView = searchItem.actionView as SearchView
|
|
||||||
searchView.maxWidth = Int.MAX_VALUE
|
|
||||||
|
|
||||||
// Fixes problem with the overflow icon showing up in lieu of search
|
|
||||||
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
|
|
||||||
|
|
||||||
if (query.isNotEmpty()) {
|
|
||||||
searchItem.expandActionView()
|
|
||||||
searchView.setQuery(query, true)
|
|
||||||
searchView.clearFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
searchView.queryTextChanges()
|
|
||||||
.filter { router.backstack.lastOrNull()?.controller == this }
|
|
||||||
.onEach {
|
|
||||||
query = it.toString()
|
|
||||||
presenter.search(query)
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +1,43 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension
|
package eu.kanade.tachiyomi.ui.browse.extension
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.presentation.browse.ExtensionState
|
import eu.kanade.presentation.browse.ExtensionState
|
||||||
import eu.kanade.presentation.browse.ExtensionsState
|
import eu.kanade.presentation.browse.ExtensionsState
|
||||||
import eu.kanade.presentation.browse.ExtensionsStateImpl
|
import eu.kanade.presentation.browse.ExtensionsStateImpl
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionsPresenter(
|
class ExtensionsPresenter(
|
||||||
|
private val presenterScope: CoroutineScope,
|
||||||
private val state: ExtensionsStateImpl = ExtensionState() as ExtensionsStateImpl,
|
private val state: ExtensionsStateImpl = ExtensionState() as ExtensionsStateImpl,
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
private val getExtensions: GetExtensionsByType = Injekt.get(),
|
private val getExtensions: GetExtensionsByType = Injekt.get(),
|
||||||
) : BasePresenter<ExtensionsController>(), ExtensionsState by state {
|
) : ExtensionsState by state {
|
||||||
|
|
||||||
private val _query: MutableStateFlow<String> = MutableStateFlow("")
|
private val _query: MutableStateFlow<String> = MutableStateFlow("")
|
||||||
|
|
||||||
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
fun onCreate() {
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
val extensionMapper: (Map<String, InstallStep>) -> ((Extension) -> ExtensionUiModel) = { map ->
|
val extensionMapper: (Map<String, InstallStep>) -> ((Extension) -> ExtensionUiModel) = { map ->
|
||||||
{
|
{
|
||||||
@ -114,6 +116,10 @@ class ExtensionsPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
presenterScope.launchIO { findAvailableExtensions() }
|
presenterScope.launchIO { findAvailableExtensions() }
|
||||||
|
|
||||||
|
preferences.extensionUpdatesCount().asFlow()
|
||||||
|
.onEach { state.updates = it }
|
||||||
|
.launchIn(presenterScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String) {
|
fun search(query: String) {
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.extension
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.bluelinelabs.conductor.Router
|
||||||
|
import eu.kanade.presentation.browse.BrowseTab
|
||||||
|
import eu.kanade.presentation.browse.ExtensionScreen
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun extensionsTab(
|
||||||
|
router: Router?,
|
||||||
|
presenter: ExtensionsPresenter,
|
||||||
|
) = BrowseTab(
|
||||||
|
titleRes = R.string.label_extensions,
|
||||||
|
badgeNumber = presenter.updates.takeIf { it > 0 },
|
||||||
|
actions = listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_search),
|
||||||
|
icon = Icons.Outlined.Search,
|
||||||
|
onClick = {
|
||||||
|
// TODO: extensions search
|
||||||
|
// presenter.search(query)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_filter),
|
||||||
|
icon = Icons.Outlined.FilterList,
|
||||||
|
onClick = { router?.pushController(ExtensionFilterController()) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content = {
|
||||||
|
ExtensionScreen(
|
||||||
|
presenter = presenter,
|
||||||
|
onLongClickItem = { extension ->
|
||||||
|
when (extension) {
|
||||||
|
is Extension.Available -> presenter.installExtension(extension)
|
||||||
|
else -> presenter.uninstallExtension(extension.pkgName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickItemCancel = { extension ->
|
||||||
|
presenter.cancelInstallUpdateExtension(extension)
|
||||||
|
},
|
||||||
|
onClickUpdateAll = {
|
||||||
|
presenter.updateAllExtensions()
|
||||||
|
},
|
||||||
|
onInstallExtension = {
|
||||||
|
presenter.installExtension(it)
|
||||||
|
},
|
||||||
|
onOpenExtension = {
|
||||||
|
router?.pushController(ExtensionDetailsController(it.pkgName))
|
||||||
|
},
|
||||||
|
onTrustExtension = {
|
||||||
|
presenter.trustSignature(it.signatureHash)
|
||||||
|
},
|
||||||
|
onUninstallExtension = {
|
||||||
|
presenter.uninstallExtension(it.pkgName)
|
||||||
|
},
|
||||||
|
onUpdateExtension = {
|
||||||
|
presenter.updateExtension(it)
|
||||||
|
},
|
||||||
|
onRefresh = {
|
||||||
|
presenter.findAvailableExtensions()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -1,144 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.feed
|
|
||||||
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.presentation.browse.FeedScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import exh.savedsearches.models.FeedSavedSearch
|
|
||||||
import exh.savedsearches.models.SavedSearch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This controller shows and manages the different search result in global search.
|
|
||||||
* This controller should only handle UI actions, IO actions should be done by [FeedPresenter]
|
|
||||||
* [FeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
|
||||||
*/
|
|
||||||
class FeedController : ComposeController<FeedPresenter>() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
|
||||||
return applicationContext?.getString(R.string.feed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the [FeedPresenter] used in controller.
|
|
||||||
*
|
|
||||||
* @return instance of [FeedPresenter]
|
|
||||||
*/
|
|
||||||
override fun createPresenter(): FeedPresenter {
|
|
||||||
return FeedPresenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
|
||||||
FeedScreen(
|
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
presenter = presenter,
|
|
||||||
onClickSavedSearch = ::onSavedSearchClick,
|
|
||||||
onClickSource = ::onSourceClick,
|
|
||||||
onClickDelete = ::onRemoveClick,
|
|
||||||
onClickManga = ::onMangaClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.feed, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_add_feed -> addFeed()
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFeed() {
|
|
||||||
viewScope.launchUI {
|
|
||||||
if (presenter.hasTooManyFeeds()) {
|
|
||||||
activity?.toast(R.string.too_many_in_feed)
|
|
||||||
return@launchUI
|
|
||||||
}
|
|
||||||
val items = presenter.getEnabledSources()
|
|
||||||
val itemsStrings = items.map { it.toString() }
|
|
||||||
var selectedIndex = 0
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.feed)
|
|
||||||
.setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which ->
|
|
||||||
selectedIndex = which
|
|
||||||
}
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
addFeedSearch(items[selectedIndex])
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFeedSearch(source: CatalogueSource) {
|
|
||||||
viewScope.launchUI {
|
|
||||||
val items = presenter.getSourceSavedSearches(source.id)
|
|
||||||
val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name }
|
|
||||||
var selectedIndex = 0
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.feed)
|
|
||||||
.setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which ->
|
|
||||||
selectedIndex = which
|
|
||||||
}
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
presenter.createFeed(source, items.getOrNull(selectedIndex - 1))
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when manga in global search is clicked, opens manga.
|
|
||||||
*
|
|
||||||
* @param manga clicked item containing manga information.
|
|
||||||
*/
|
|
||||||
private fun onMangaClick(manga: eu.kanade.domain.manga.model.Manga) {
|
|
||||||
// Open MangaController.
|
|
||||||
parentController?.router?.pushController(MangaController(manga.id, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a catalogue with the given search.
|
|
||||||
*/
|
|
||||||
private fun onSourceClick(source: CatalogueSource) {
|
|
||||||
presenter.preferences.lastUsedSource().set(source.id)
|
|
||||||
parentController?.router?.pushController(LatestUpdatesController(source))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSavedSearchClick(savedSearch: SavedSearch, source: CatalogueSource) {
|
|
||||||
presenter.preferences.lastUsedSource().set(savedSearch.source)
|
|
||||||
parentController?.router?.pushController(BrowseSourceController(source, savedSearch = savedSearch.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onRemoveClick(feedSavedSearch: FeedSavedSearch) {
|
|
||||||
MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.feed)
|
|
||||||
.setMessage(R.string.feed_delete)
|
|
||||||
.setPositiveButton(R.string.action_delete) { _, _ ->
|
|
||||||
presenter.deleteFeed(feedSavedSearch)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.feed
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
import eu.kanade.domain.manga.interactor.InsertManga
|
import eu.kanade.domain.manga.interactor.InsertManga
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
@ -24,13 +23,13 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.savedsearches.models.FeedSavedSearch
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
import exh.savedsearches.models.SavedSearch
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -55,6 +54,7 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
|||||||
* @param preferences manages the preference calls.
|
* @param preferences manages the preference calls.
|
||||||
*/
|
*/
|
||||||
open class FeedPresenter(
|
open class FeedPresenter(
|
||||||
|
private val presenterScope: CoroutineScope,
|
||||||
private val state: FeedStateImpl = FeedState() as FeedStateImpl,
|
private val state: FeedStateImpl = FeedState() as FeedStateImpl,
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
@ -67,7 +67,7 @@ open class FeedPresenter(
|
|||||||
private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(),
|
private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(),
|
||||||
private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(),
|
private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(),
|
||||||
private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(),
|
private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(),
|
||||||
) : BasePresenter<FeedController>(), FeedState by state {
|
) : FeedState by state {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the different sources by user settings.
|
* Fetches the different sources by user settings.
|
||||||
@ -84,9 +84,7 @@ open class FeedPresenter(
|
|||||||
*/
|
*/
|
||||||
private var fetchImageSubscription: Subscription? = null
|
private var fetchImageSubscription: Subscription? = null
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
fun onCreate() {
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
getFeedSavedSearchGlobal.subscribe()
|
getFeedSavedSearchGlobal.subscribe()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach {
|
||||||
@ -106,10 +104,24 @@ open class FeedPresenter(
|
|||||||
.launchIn(presenterScope)
|
.launchIn(presenterScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
fun onDestroy() {
|
||||||
fetchSourcesSubscription?.unsubscribe()
|
fetchSourcesSubscription?.unsubscribe()
|
||||||
fetchImageSubscription?.unsubscribe()
|
fetchImageSubscription?.unsubscribe()
|
||||||
super.onDestroy()
|
}
|
||||||
|
|
||||||
|
fun openAddDialog() {
|
||||||
|
presenterScope.launchIO {
|
||||||
|
if (hasTooManyFeeds()) {
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
dialog = Dialog.AddFeed(getEnabledSources())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openAddSearchDialog(source: CatalogueSource) {
|
||||||
|
presenterScope.launchIO {
|
||||||
|
dialog = Dialog.AddFeedSearch(source, (if (source.supportsLatest) listOf(null) else emptyList()) + getSourceSavedSearches(source.id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun hasTooManyFeeds(): Boolean {
|
suspend fun hasTooManyFeeds(): Boolean {
|
||||||
@ -330,4 +342,10 @@ open class FeedPresenter(
|
|||||||
}
|
}
|
||||||
return localManga?.toDbManga()!!
|
return localManga?.toDbManga()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class Dialog {
|
||||||
|
data class AddFeed(val options: List<CatalogueSource>) : Dialog()
|
||||||
|
data class AddFeedSearch(val source: CatalogueSource, val options: List<SavedSearch?>) : Dialog()
|
||||||
|
data class DeleteFeed(val feed: FeedSavedSearch) : Dialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Add
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.bluelinelabs.conductor.Router
|
||||||
|
import eu.kanade.presentation.browse.BrowseTab
|
||||||
|
import eu.kanade.presentation.browse.FeedScreen
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun feedTab(
|
||||||
|
router: Router?,
|
||||||
|
presenter: FeedPresenter,
|
||||||
|
) = BrowseTab(
|
||||||
|
titleRes = R.string.feed,
|
||||||
|
actions = listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_add),
|
||||||
|
icon = Icons.Outlined.Add,
|
||||||
|
onClick = {
|
||||||
|
presenter.openAddDialog()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content = {
|
||||||
|
FeedScreen(
|
||||||
|
presenter = presenter,
|
||||||
|
onClickAdd = {
|
||||||
|
presenter.openAddSearchDialog(it)
|
||||||
|
},
|
||||||
|
onClickCreate = { source, savedSearch ->
|
||||||
|
presenter.createFeed(source, savedSearch)
|
||||||
|
},
|
||||||
|
onClickSavedSearch = { savedSearch, source ->
|
||||||
|
presenter.preferences.lastUsedSource().set(savedSearch.source)
|
||||||
|
router?.pushController(BrowseSourceController(source, savedSearch = savedSearch.id))
|
||||||
|
},
|
||||||
|
onClickSource = { source ->
|
||||||
|
presenter.preferences.lastUsedSource().set(source.id)
|
||||||
|
router?.pushController(LatestUpdatesController(source))
|
||||||
|
},
|
||||||
|
onClickDelete = {
|
||||||
|
presenter.dialog = FeedPresenter.Dialog.DeleteFeed(it)
|
||||||
|
},
|
||||||
|
onClickDeleteConfirm = {
|
||||||
|
presenter.deleteFeed(it)
|
||||||
|
},
|
||||||
|
onClickManga = { manga ->
|
||||||
|
router?.pushController(MangaController(manga.id, true))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.bluelinelabs.conductor.Router
|
||||||
|
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||||
|
import eu.kanade.presentation.browse.BrowseTab
|
||||||
|
import eu.kanade.presentation.browse.MigrateSourceScreen
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun migrateSourcesTab(
|
||||||
|
router: Router?,
|
||||||
|
presenter: MigrationSourcesPresenter,
|
||||||
|
): BrowseTab {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
return BrowseTab(
|
||||||
|
titleRes = R.string.label_migration,
|
||||||
|
actions = listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.migration_help_guide),
|
||||||
|
icon = Icons.Outlined.HelpOutline,
|
||||||
|
onClick = {
|
||||||
|
uriHandler.openUri("https://tachiyomi.org/help/guides/source-migration/")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content = {
|
||||||
|
MigrateSourceScreen(
|
||||||
|
presenter = presenter,
|
||||||
|
onClickItem = { source ->
|
||||||
|
router?.pushController(
|
||||||
|
MigrationMangaController(
|
||||||
|
source.id,
|
||||||
|
source.name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// SY -->
|
||||||
|
onClickAll = { source ->
|
||||||
|
// TODO: Jay wtf, need to clean this up sometime
|
||||||
|
launchIO {
|
||||||
|
val manga = Injekt.get<GetFavorites>().await()
|
||||||
|
val sourceMangas =
|
||||||
|
manga.asSequence().filter { it.source == source.id }.map { it.id }.toList()
|
||||||
|
withUIContext {
|
||||||
|
if (router != null) {
|
||||||
|
PreMigrationController.navigateToMigration(
|
||||||
|
Injekt.get<PreferencesHelper>().skipPreMigration().get(),
|
||||||
|
router,
|
||||||
|
sourceMangas,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,99 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import eu.kanade.presentation.browse.BrowseTabWrapper
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.presentation.browse.MigrateSourceScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>() {
|
class MigrationSourcesController : FullComposeController<MigrationSourcesPresenterWrapper>() {
|
||||||
|
|
||||||
init {
|
override fun createPresenter() = MigrationSourcesPresenterWrapper()
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPresenter() = MigrationSourcesPresenter()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
override fun ComposeContent() {
|
||||||
MigrateSourceScreen(
|
BrowseTabWrapper(migrateSourcesTab(router, presenter = presenter.presenter))
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
presenter = presenter,
|
|
||||||
onClickItem = { source ->
|
|
||||||
val parentController = parentController
|
|
||||||
if (parentController is BrowseController) {
|
|
||||||
parentController.router
|
|
||||||
} else {
|
|
||||||
router
|
|
||||||
}.pushController(
|
|
||||||
MigrationMangaController(
|
|
||||||
source.id,
|
|
||||||
source.name,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClickAll = { source ->
|
|
||||||
// TODO: Jay wtf, need to clean this up sometime
|
|
||||||
launchIO {
|
|
||||||
val manga = Injekt.get<GetFavorites>().await()
|
|
||||||
val sourceMangas =
|
|
||||||
manga.asSequence().filter { it.source == source.id }.map { it.id }.toList()
|
|
||||||
withUIContext {
|
|
||||||
PreMigrationController.navigateToMigration(
|
|
||||||
Injekt.get<PreferencesHelper>().skipPreMigration().get(),
|
|
||||||
run {
|
|
||||||
val parentController = parentController
|
|
||||||
if (parentController is BrowseController) {
|
|
||||||
parentController.router
|
|
||||||
} else {
|
|
||||||
router
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sourceMangas,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) =
|
|
||||||
inflater.inflate(R.menu.browse_migrate, menu)
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
return when (val itemId = item.itemId) {
|
|
||||||
R.id.action_source_migration_help -> {
|
|
||||||
activity?.openInBrowser(HELP_URL)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.asc_alphabetical,
|
|
||||||
R.id.desc_alphabetical,
|
|
||||||
-> {
|
|
||||||
presenter.setAlphabeticalSorting(itemId == R.id.asc_alphabetical)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.asc_count,
|
|
||||||
R.id.desc_count,
|
|
||||||
-> {
|
|
||||||
presenter.setTotalSorting(itemId == R.id.asc_count)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +1,35 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.presentation.browse.MigrateSourceState
|
import eu.kanade.presentation.browse.MigrateSourceState
|
||||||
import eu.kanade.presentation.browse.MigrateSourceStateImpl
|
import eu.kanade.presentation.browse.MigrateSourceStateImpl
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
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
|
||||||
|
|
||||||
class MigrationSourcesPresenter(
|
class MigrationSourcesPresenter(
|
||||||
|
private val presenterScope: CoroutineScope,
|
||||||
private val state: MigrateSourceStateImpl = MigrateSourceState() as MigrateSourceStateImpl,
|
private val state: MigrateSourceStateImpl = MigrateSourceState() as MigrateSourceStateImpl,
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
||||||
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
||||||
) : BasePresenter<MigrationSourcesController>(), MigrateSourceState by state {
|
) : MigrateSourceState by state {
|
||||||
|
|
||||||
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
||||||
val channel = _channel.receiveAsFlow()
|
val channel = _channel.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
fun onCreate() {
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getSourcesWithFavoriteCount.subscribe()
|
getSourcesWithFavoriteCount.subscribe()
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
@ -39,14 +41,32 @@ class MigrationSourcesPresenter(
|
|||||||
state.isLoading = false
|
state.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preferences.migrationSortingDirection().asFlow()
|
||||||
|
.onEach { state.sortingDirection = it }
|
||||||
|
.launchIn(presenterScope)
|
||||||
|
|
||||||
|
preferences.migrationSortingMode().asFlow()
|
||||||
|
.onEach { state.sortingMode = it }
|
||||||
|
.launchIn(presenterScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAlphabeticalSorting(isAscending: Boolean) {
|
fun toggleSortingMode() {
|
||||||
setMigrateSorting.await(SetMigrateSorting.Mode.ALPHABETICAL, isAscending)
|
val newMode = when (state.sortingMode) {
|
||||||
|
SetMigrateSorting.Mode.ALPHABETICAL -> SetMigrateSorting.Mode.TOTAL
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> SetMigrateSorting.Mode.ALPHABETICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
setMigrateSorting.await(newMode, state.sortingDirection)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTotalSorting(isAscending: Boolean) {
|
fun toggleSortingDirection() {
|
||||||
setMigrateSorting.await(SetMigrateSorting.Mode.TOTAL, isAscending)
|
val newDirection = when (state.sortingDirection) {
|
||||||
|
SetMigrateSorting.Direction.ASCENDING -> SetMigrateSorting.Direction.DESCENDING
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> SetMigrateSorting.Direction.ASCENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
setMigrateSorting.await(state.sortingMode, newDirection)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Event {
|
sealed class Event {
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
|
||||||
|
class MigrationSourcesPresenterWrapper() : BasePresenter<MigrationSourcesController>() {
|
||||||
|
|
||||||
|
val presenter = MigrationSourcesPresenter(presenterScope)
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
presenter.onCreate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,46 +3,18 @@ package eu.kanade.tachiyomi.ui.browse.source
|
|||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import eu.kanade.presentation.browse.BrowseTabWrapper
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.presentation.browse.SourcesScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.SearchableComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.util.system.getParcelableCompat
|
import eu.kanade.tachiyomi.util.system.getParcelableCompat
|
||||||
import exh.ui.smartsearch.SmartSearchController
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SourcesController(bundle: Bundle? = null) : SearchableComposeController<SourcesPresenter>(bundle) {
|
class SourcesController(bundle: Bundle? = null) : FullComposeController<SourcesPresenterWrapper>(bundle) {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
// EXH -->
|
|
||||||
private val smartSearchConfig = args.getParcelableCompat<SmartSearchConfig>(SMART_SEARCH_CONFIG)
|
private val smartSearchConfig = args.getParcelableCompat<SmartSearchConfig>(SMART_SEARCH_CONFIG)
|
||||||
|
|
||||||
private val mode = if (smartSearchConfig == null) Mode.CATALOGUE else Mode.SMART_SEARCH
|
private val mode = if (smartSearchConfig == null) Mode.CATALOGUE else Mode.SMART_SEARCH
|
||||||
// EXH <--
|
|
||||||
|
|
||||||
init {
|
|
||||||
// SY -->
|
|
||||||
setHasOptionsMenu(mode == Mode.CATALOGUE)
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -53,40 +25,11 @@ class SourcesController(bundle: Bundle? = null) : SearchableComposeController<So
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPresenter() = SourcesPresenter(/* SY --> */ controllerMode = mode /* SY <-- */)
|
override fun createPresenter() = SourcesPresenterWrapper(controllerMode = mode, smartSearchConfig = smartSearchConfig)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
override fun ComposeContent() {
|
||||||
SourcesScreen(
|
BrowseTabWrapper(sourcesTab(router, presenter = presenter.presenter))
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
|
||||||
presenter = presenter,
|
|
||||||
onClickItem = { source ->
|
|
||||||
when {
|
|
||||||
mode == Mode.SMART_SEARCH -> router.pushController(SmartSearchController(source.id, smartSearchConfig!!))
|
|
||||||
preferences.useNewSourceNavigation().get() -> openSource(source, SourceFeedController(source.id))
|
|
||||||
else -> openSource(source, BrowseSourceController(source))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClickDisable = { source ->
|
|
||||||
presenter.toggleSource(source)
|
|
||||||
},
|
|
||||||
onClickLatest = { source ->
|
|
||||||
openSource(source, LatestUpdatesController(source))
|
|
||||||
},
|
|
||||||
onClickPin = { source ->
|
|
||||||
presenter.togglePin(source)
|
|
||||||
},
|
|
||||||
onClickSetCategories = { source, categories ->
|
|
||||||
presenter.setSourceCategories(source, categories)
|
|
||||||
},
|
|
||||||
onClickToggleDataSaver = { source ->
|
|
||||||
presenter.toggleExcludeFromDataSaver(source)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
(activity as? MainActivity)?.ready = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
@ -94,57 +37,6 @@ class SourcesController(bundle: Bundle? = null) : SearchableComposeController<So
|
|||||||
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
|
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a catalogue with the given controller.
|
|
||||||
*/
|
|
||||||
private fun openSource(source: Source, controller: Controller) {
|
|
||||||
if (!preferences.incognitoMode().get()) {
|
|
||||||
preferences.lastUsedSource().set(source.id)
|
|
||||||
}
|
|
||||||
parentController!!.router.pushController(controller)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an option menu item has been selected by the user.
|
|
||||||
*
|
|
||||||
* @param item The selected item.
|
|
||||||
* @return True if this event has been consumed, false if it has not.
|
|
||||||
*/
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
return when (item.itemId) {
|
|
||||||
// Initialize option to open catalogue settings.
|
|
||||||
R.id.action_settings -> {
|
|
||||||
parentController!!.router.pushController(SourceFilterController())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
if (mode == Mode.CATALOGUE) {
|
|
||||||
createOptionsMenu(
|
|
||||||
menu,
|
|
||||||
inflater,
|
|
||||||
R.menu.browse_sources,
|
|
||||||
R.id.action_search,
|
|
||||||
R.string.action_global_search_hint,
|
|
||||||
false, // GlobalSearch handles the searching here
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchViewQueryTextSubmit(query: String?) {
|
|
||||||
// SY -->
|
|
||||||
if (mode == Mode.CATALOGUE) {
|
|
||||||
parentController!!.router.pushController(
|
|
||||||
GlobalSearchController(query),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Parcelable
|
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Parcelable
|
||||||
|
|
||||||
@ -157,5 +49,4 @@ class SourcesController(bundle: Bundle? = null) : SearchableComposeController<So
|
|||||||
const val SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG"
|
const val SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG"
|
||||||
const val SMART_SEARCH_SOURCE_TAG = "smart_search_source_tag"
|
const val SMART_SEARCH_SOURCE_TAG = "smart_search_source_tag"
|
||||||
}
|
}
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.GetShowLatest
|
import eu.kanade.domain.source.interactor.GetShowLatest
|
||||||
import eu.kanade.domain.source.interactor.GetSourceCategories
|
import eu.kanade.domain.source.interactor.GetSourceCategories
|
||||||
@ -13,8 +12,9 @@ import eu.kanade.domain.source.model.Source
|
|||||||
import eu.kanade.presentation.browse.SourceUiModel
|
import eu.kanade.presentation.browse.SourceUiModel
|
||||||
import eu.kanade.presentation.browse.SourcesState
|
import eu.kanade.presentation.browse.SourcesState
|
||||||
import eu.kanade.presentation.browse.SourcesStateImpl
|
import eu.kanade.presentation.browse.SourcesStateImpl
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
@ -29,7 +29,9 @@ import uy.kohesive.injekt.api.get
|
|||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
|
|
||||||
class SourcesPresenter(
|
class SourcesPresenter(
|
||||||
|
private val presenterScope: CoroutineScope,
|
||||||
private val state: SourcesStateImpl = SourcesState() as SourcesStateImpl,
|
private val state: SourcesStateImpl = SourcesState() as SourcesStateImpl,
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
||||||
@ -38,15 +40,17 @@ class SourcesPresenter(
|
|||||||
private val getShowLatest: GetShowLatest = Injekt.get(),
|
private val getShowLatest: GetShowLatest = Injekt.get(),
|
||||||
private val toggleExcludeFromDataSaver: ToggleExcludeFromDataSaver = Injekt.get(),
|
private val toggleExcludeFromDataSaver: ToggleExcludeFromDataSaver = Injekt.get(),
|
||||||
private val setSourceCategories: SetSourceCategories = Injekt.get(),
|
private val setSourceCategories: SetSourceCategories = Injekt.get(),
|
||||||
private val controllerMode: SourcesController.Mode,
|
val controllerMode: SourcesController.Mode,
|
||||||
|
val smartSearchConfig: SourcesController.SmartSearchConfig?,
|
||||||
// SY <--
|
// SY <--
|
||||||
) : BasePresenter<SourcesController>(), SourcesState by state {
|
) : SourcesState by state {
|
||||||
|
|
||||||
private val _events = Channel<Event>(Int.MAX_VALUE)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
val useNewSourceNavigation = preferences.useNewSourceNavigation().get()
|
||||||
super.onCreate(savedState)
|
|
||||||
|
fun onCreate() {
|
||||||
// SY -->
|
// SY -->
|
||||||
combine(
|
combine(
|
||||||
getEnabledSources.subscribe(),
|
getEnabledSources.subscribe(),
|
||||||
@ -105,6 +109,12 @@ class SourcesPresenter(
|
|||||||
state.items = uiModels
|
state.items = uiModels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onOpenSource(source: Source) {
|
||||||
|
if (!preferences.incognitoMode().get()) {
|
||||||
|
preferences.lastUsedSource().set(source.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleSource(source: Source) {
|
fun toggleSource(source: Source) {
|
||||||
toggleSource.await(source)
|
toggleSource.await(source)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
|
||||||
|
class SourcesPresenterWrapper(controllerMode: SourcesController.Mode, smartSearchConfig: SourcesController.SmartSearchConfig?) : BasePresenter<SourcesPresenter>() {
|
||||||
|
|
||||||
|
val presenter = SourcesPresenter(presenterScope, controllerMode = controllerMode, smartSearchConfig = smartSearchConfig)
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
presenter.onCreate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.TravelExplore
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.bluelinelabs.conductor.Router
|
||||||
|
import eu.kanade.presentation.browse.BrowseTab
|
||||||
|
import eu.kanade.presentation.browse.SourcesScreen
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||||
|
import exh.ui.smartsearch.SmartSearchController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun sourcesTab(
|
||||||
|
router: Router?,
|
||||||
|
presenter: SourcesPresenter,
|
||||||
|
) = BrowseTab(
|
||||||
|
// SY -->
|
||||||
|
titleRes = when (presenter.controllerMode) {
|
||||||
|
SourcesController.Mode.CATALOGUE -> R.string.label_sources
|
||||||
|
SourcesController.Mode.SMART_SEARCH -> R.string.find_in_another_source
|
||||||
|
},
|
||||||
|
actions = if (presenter.controllerMode == SourcesController.Mode.CATALOGUE) {
|
||||||
|
listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_global_search),
|
||||||
|
icon = Icons.Outlined.TravelExplore,
|
||||||
|
onClick = { router?.pushController(GlobalSearchController()) },
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_filter),
|
||||||
|
icon = Icons.Outlined.FilterList,
|
||||||
|
onClick = { router?.pushController(SourceFilterController()) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else emptyList(),
|
||||||
|
// SY <--
|
||||||
|
content = {
|
||||||
|
SourcesScreen(
|
||||||
|
presenter = presenter,
|
||||||
|
onClickItem = { source ->
|
||||||
|
// SY -->
|
||||||
|
val controller = when {
|
||||||
|
presenter.controllerMode == SourcesController.Mode.SMART_SEARCH ->
|
||||||
|
SmartSearchController(source.id, presenter.smartSearchConfig!!)
|
||||||
|
presenter.useNewSourceNavigation -> SourceFeedController(source.id)
|
||||||
|
else -> BrowseSourceController(source)
|
||||||
|
}
|
||||||
|
presenter.onOpenSource(source)
|
||||||
|
router?.pushController(controller)
|
||||||
|
// SY <--
|
||||||
|
},
|
||||||
|
onClickDisable = { source ->
|
||||||
|
presenter.toggleSource(source)
|
||||||
|
},
|
||||||
|
onClickLatest = { source ->
|
||||||
|
presenter.onOpenSource(source)
|
||||||
|
router?.pushController(LatestUpdatesController(source))
|
||||||
|
},
|
||||||
|
onClickPin = { source ->
|
||||||
|
presenter.togglePin(source)
|
||||||
|
},
|
||||||
|
// SY -->
|
||||||
|
onClickSetCategories = { source, categories ->
|
||||||
|
presenter.setSourceCategories(source, categories)
|
||||||
|
},
|
||||||
|
onClickToggleDataSaver = { source ->
|
||||||
|
presenter.toggleExcludeFromDataSaver(source)
|
||||||
|
},
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -46,7 +46,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeContentController
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeContentController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||||
@ -189,7 +188,7 @@ class MainActivity : BaseActivity() {
|
|||||||
R.id.nav_library -> router.setRoot(LibraryController(), id)
|
R.id.nav_library -> router.setRoot(LibraryController(), id)
|
||||||
R.id.nav_updates -> router.setRoot(UpdatesController(), id)
|
R.id.nav_updates -> router.setRoot(UpdatesController(), id)
|
||||||
R.id.nav_history -> router.setRoot(HistoryController(), id)
|
R.id.nav_history -> router.setRoot(HistoryController(), id)
|
||||||
R.id.nav_browse -> router.setRoot(BrowseController(), id)
|
R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id)
|
||||||
R.id.nav_more -> router.setRoot(MoreController(), id)
|
R.id.nav_more -> router.setRoot(MoreController(), id)
|
||||||
}
|
}
|
||||||
} else if (!isHandlingShortcut) {
|
} else if (!isHandlingShortcut) {
|
||||||
@ -644,17 +643,6 @@ class MainActivity : BaseActivity() {
|
|||||||
showNav(true)
|
showNav(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from is TabbedController) {
|
|
||||||
from.cleanupTabs(binding.tabs)
|
|
||||||
}
|
|
||||||
if (internalTo is TabbedController) {
|
|
||||||
if (internalTo.configureTabs(binding.tabs)) {
|
|
||||||
binding.tabs.isVisible = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.tabs.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from is FabController) {
|
if (from is FabController) {
|
||||||
from.cleanupFab(binding.fabLayout.rootFab)
|
from.cleanupFab(binding.fabLayout.rootFab)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,7 +114,7 @@ class SettingsDebugController : BasicFullComposeController() {
|
|||||||
fun FunctionList(
|
fun FunctionList(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
functions: List<Pair<KFunction<*>, String>>,
|
functions: List<Pair<KFunction<*>, String>>,
|
||||||
toggles: List<DebugToggle>
|
toggles: List<DebugToggle>,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
|
|
||||||
</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.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
|
|
||||||
</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.3,16.9c0.4,-0.7 0.7,-1.5 0.7,-2.4c0,-2.5 -2,-4.5 -4.5,-4.5S11,12 11,14.5s2,4.5 4.5,4.5c0.9,0 1.7,-0.3 2.4,-0.7l3.2,3.2l1.4,-1.4L19.3,16.9zM15.5,17c-1.4,0 -2.5,-1.1 -2.5,-2.5s1.1,-2.5 2.5,-2.5s2.5,1.1 2.5,2.5S16.9,17 15.5,17zM12,20v2C6.48,22 2,17.52 2,12C2,6.48 6.48,2 12,2c4.84,0 8.87,3.44 9.8,8h-2.07c-0.64,-2.46 -2.4,-4.47 -4.73,-5.41V5c0,1.1 -0.9,2 -2,2h-2v2c0,0.55 -0.45,1 -1,1H8v2h2v3H9l-4.79,-4.79C4.08,10.79 4,11.38 4,12C4,16.41 7.59,20 12,20z"/>
|
|
||||||
</vector>
|
|
||||||
@ -26,11 +26,6 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme" />
|
android:theme="?attr/actionBarTheme" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tabs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/downloaded_only"
|
android:id="@+id/downloaded_only"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@ -25,12 +25,6 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme" />
|
android:theme="?attr/actionBarTheme" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tabs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/downloaded_only"
|
android:id="@+id/downloaded_only"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_search"
|
|
||||||
android:icon="@drawable/ic_search_24dp"
|
|
||||||
android:title="@string/action_search"
|
|
||||||
app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="collapseActionView|ifRoom" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_settings"
|
|
||||||
android:icon="@drawable/ic_translate_24dp"
|
|
||||||
android:title="@string/action_filter"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort"
|
|
||||||
android:icon="@drawable/ic_sort_24dp"
|
|
||||||
android:title="@string/action_sort"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="collapseActionView|ifRoom" >
|
|
||||||
<menu>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort_alphabetical"
|
|
||||||
android:title="@string/action_sort_alpha"
|
|
||||||
app:showAsAction="never">
|
|
||||||
<menu>
|
|
||||||
<item
|
|
||||||
android:id="@+id/asc_alphabetical"
|
|
||||||
android:title="@string/action_asc" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/desc_alphabetical"
|
|
||||||
android:title="@string/action_desc" />
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_sort_count"
|
|
||||||
android:title="@string/action_sort_count"
|
|
||||||
app:showAsAction="never">
|
|
||||||
<menu>
|
|
||||||
<item
|
|
||||||
android:id="@+id/asc_count"
|
|
||||||
android:title="@string/action_asc" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/desc_count"
|
|
||||||
android:title="@string/action_desc" />
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_source_migration_help"
|
|
||||||
android:icon="@drawable/ic_help_24dp"
|
|
||||||
android:title="@string/migration_help_guide"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_search"
|
|
||||||
android:icon="@drawable/ic_travel_explore_24dp"
|
|
||||||
android:title="@string/action_global_search"
|
|
||||||
app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="collapseActionView|ifRoom" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_settings"
|
|
||||||
android:icon="@drawable/ic_filter_list_24dp"
|
|
||||||
android:title="@string/action_filter"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@ -64,7 +64,6 @@ directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
|||||||
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||||
|
|
||||||
conductor-core = { module = "com.bluelinelabs:conductor", version.ref = "conductor_version" }
|
conductor-core = { module = "com.bluelinelabs:conductor", version.ref = "conductor_version" }
|
||||||
conductor-viewpager = { module = "com.bluelinelabs:conductor-viewpager", version.ref = "conductor_version" }
|
|
||||||
conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version.ref = "conductor_version" }
|
conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version.ref = "conductor_version" }
|
||||||
|
|
||||||
flowbinding-android = { module = "io.github.reactivecircus.flowbinding:flowbinding-android", version.ref = "flowbinding_version" }
|
flowbinding-android = { module = "io.github.reactivecircus.flowbinding:flowbinding-android", version.ref = "flowbinding_version" }
|
||||||
@ -99,7 +98,7 @@ sqlite = ["sqlitektx", "sqlite-android"]
|
|||||||
nucleus = ["nucleus-core", "nucleus-supportv7"]
|
nucleus = ["nucleus-core", "nucleus-supportv7"]
|
||||||
coil = ["coil-core", "coil-gif", "coil-compose"]
|
coil = ["coil-core", "coil-gif", "coil-compose"]
|
||||||
flowbinding = ["flowbinding-android", "flowbinding-appcompat"]
|
flowbinding = ["flowbinding-android", "flowbinding-appcompat"]
|
||||||
conductor = ["conductor-core", "conductor-viewpager", "conductor-support-preference"]
|
conductor = ["conductor-core", "conductor-support-preference"]
|
||||||
shizuku = ["shizuku-api", "shizuku-provider"]
|
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user