mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-12 20:19:05 +01:00
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
This commit is contained in:
@@ -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,
|
||||
)
|
||||
@@ -22,15 +22,12 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -57,7 +54,6 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
|
||||
@Composable
|
||||
fun ExtensionScreen(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
presenter: ExtensionsPresenter,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
@@ -68,10 +64,8 @@ fun ExtensionScreen(
|
||||
onOpenExtension: (Extension.Installed) -> Unit,
|
||||
onClickUpdateAll: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onLaunched: () -> Unit,
|
||||
) {
|
||||
SwipeRefresh(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
state = rememberSwipeRefreshState(presenter.isRefreshing),
|
||||
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
|
||||
onRefresh = onRefresh,
|
||||
@@ -90,7 +84,6 @@ fun ExtensionScreen(
|
||||
onTrustExtension = onTrustExtension,
|
||||
onOpenExtension = onOpenExtension,
|
||||
onClickUpdateAll = onClickUpdateAll,
|
||||
onLaunched = onLaunched,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +101,6 @@ fun ExtensionContent(
|
||||
onTrustExtension: (Extension.Untrusted) -> Unit,
|
||||
onOpenExtension: (Extension.Installed) -> Unit,
|
||||
onClickUpdateAll: () -> Unit,
|
||||
onLaunched: () -> Unit,
|
||||
) {
|
||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||
|
||||
@@ -187,9 +179,6 @@ fun ExtensionContent(
|
||||
}
|
||||
},
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
onLaunched()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ interface ExtensionsState {
|
||||
val isLoading: Boolean
|
||||
val isRefreshing: Boolean
|
||||
val items: List<ExtensionUiModel>
|
||||
val updates: Int
|
||||
val isEmpty: Boolean
|
||||
}
|
||||
|
||||
@@ -21,5 +22,6 @@ class ExtensionsStateImpl : ExtensionsState {
|
||||
override var isLoading: Boolean by mutableStateOf(true)
|
||||
override var isRefreshing: Boolean by mutableStateOf(false)
|
||||
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
|
||||
override var updates: Int by mutableStateOf(0)
|
||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.browse.components.SourceIcon
|
||||
@@ -39,7 +39,6 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
|
||||
@Composable
|
||||
fun MigrateSourceScreen(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
presenter: MigrationSourcesPresenter,
|
||||
onClickItem: (Source) -> Unit,
|
||||
) {
|
||||
@@ -49,28 +48,44 @@ fun MigrateSourceScreen(
|
||||
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
|
||||
else ->
|
||||
MigrateSourceList(
|
||||
nestedScrollInterop = nestedScrollInterop,
|
||||
list = presenter.items,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = { source ->
|
||||
val sourceId = source.id.toString()
|
||||
context.copyToClipboard(sourceId, sourceId)
|
||||
},
|
||||
sortingMode = presenter.sortingMode,
|
||||
onToggleSortingMode = { presenter.toggleSortingMode() },
|
||||
sortingDirection = presenter.sortingDirection,
|
||||
onToggleSortingDirection = { presenter.toggleSortingDirection() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MigrateSourceList(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
list: List<Pair<Source, Long>>,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onLongClickItem: (Source) -> Unit,
|
||||
sortingMode: SetMigrateSorting.Mode,
|
||||
onToggleSortingMode: () -> Unit,
|
||||
sortingDirection: SetMigrateSorting.Direction,
|
||||
onToggleSortingDirection: () -> Unit,
|
||||
) {
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
) {
|
||||
stickyHeader {
|
||||
Row {
|
||||
Button(onClick = onToggleSortingMode) {
|
||||
Text(sortingMode.toString())
|
||||
}
|
||||
Button(onClick = onToggleSortingDirection) {
|
||||
Text(sortingDirection.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "title") {
|
||||
Text(
|
||||
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.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.model.Source
|
||||
|
||||
interface MigrateSourceState {
|
||||
val isLoading: Boolean
|
||||
val items: List<Pair<Source, Long>>
|
||||
val isEmpty: Boolean
|
||||
val sortingMode: SetMigrateSorting.Mode
|
||||
val sortingDirection: SetMigrateSorting.Direction
|
||||
}
|
||||
|
||||
fun MigrateSourceState(): MigrateSourceState {
|
||||
@@ -20,4 +23,6 @@ class MigrateSourceStateImpl : MigrateSourceState {
|
||||
override var isLoading: Boolean by mutableStateOf(true)
|
||||
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -40,14 +38,12 @@ import eu.kanade.presentation.util.topPaddingValues
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter.Dialog
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
@Composable
|
||||
fun SourcesScreen(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
presenter: SourcesPresenter,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onClickDisable: (Source) -> Unit,
|
||||
@@ -60,7 +56,6 @@ fun SourcesScreen(
|
||||
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
|
||||
else -> {
|
||||
SourceList(
|
||||
nestedScrollConnection = nestedScrollInterop,
|
||||
state = presenter,
|
||||
onClickItem = onClickItem,
|
||||
onClickDisable = onClickDisable,
|
||||
@@ -82,7 +77,6 @@ fun SourcesScreen(
|
||||
|
||||
@Composable
|
||||
fun SourceList(
|
||||
nestedScrollConnection: NestedScrollConnection,
|
||||
state: SourcesState,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onClickDisable: (Source) -> Unit,
|
||||
@@ -90,7 +84,6 @@ fun SourceList(
|
||||
onClickPin: (Source) -> Unit,
|
||||
) {
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
||||
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
) {
|
||||
items(
|
||||
@@ -119,7 +112,7 @@ fun SourceList(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
source = model.source,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = { state.dialog = Dialog(it) },
|
||||
onLongClickItem = { state.dialog = SourcesPresenter.Dialog(it) },
|
||||
onClickLatest = onClickLatest,
|
||||
onClickPin = onClickPin,
|
||||
)
|
||||
|
||||
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.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.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.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.sp
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
|
||||
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
|
||||
|
||||
@Composable
|
||||
@@ -46,13 +37,7 @@ fun LibraryTabs(
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = state.currentPage,
|
||||
edgePadding = 0.dp,
|
||||
indicator = { tabPositions ->
|
||||
TabRowDefaults.Indicator(
|
||||
Modifier
|
||||
.tabIndicatorOffset(tabPositions[state.currentPage])
|
||||
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
|
||||
)
|
||||
},
|
||||
indicator = { TabIndicator(it[state.currentPage]) },
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
val count by if (showMangaCount) {
|
||||
@@ -64,21 +49,7 @@ fun LibraryTabs(
|
||||
selected = state.currentPage == index,
|
||||
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
||||
text = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = category.visualName,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
TabText(category.visualName, count, state.currentPage == index)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user