mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2026-01-24 12:34:04 +01:00
Use side buttons instead of tabs for source browsing
This commit is contained in:
@@ -20,7 +20,6 @@ import ca.gosyer.data.server.interactions.LibraryInteractionHandler
|
||||
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
||||
import ca.gosyer.data.server.interactions.SourceInteractionHandler
|
||||
import ca.gosyer.data.ui.UiPreferences
|
||||
import io.ktor.client.HttpClient
|
||||
import toothpick.ktp.binding.bind
|
||||
import toothpick.ktp.binding.module
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
@Composable
|
||||
fun ErrorScreen(errorMessage: String? = null) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
|
||||
@@ -23,13 +23,19 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import ca.gosyer.ui.main.Routing
|
||||
import com.github.zsoltk.compose.router.BackStack
|
||||
import compose.icons.FontAwesomeIcons
|
||||
import compose.icons.fontawesomeicons.Regular
|
||||
import compose.icons.fontawesomeicons.regular.WindowClose
|
||||
|
||||
@Composable
|
||||
fun <T> Toolbar(router: BackStack<T>, name: String, closable: Boolean, search: ((String) -> Unit)? = null) {
|
||||
fun Toolbar(
|
||||
name: String,
|
||||
router: BackStack<Routing>? = null,
|
||||
closable: Boolean,
|
||||
search: ((String) -> Unit)? = null
|
||||
) {
|
||||
val searchText = remember { mutableStateOf("") }
|
||||
Surface(Modifier.fillMaxWidth().height(32.dp), elevation = 2.dp) {
|
||||
Row(Modifier.fillMaxSize(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
@@ -46,7 +52,7 @@ fun <T> Toolbar(router: BackStack<T>, name: String, closable: Boolean, search: (
|
||||
if (closable) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
router.pop()
|
||||
router?.pop()
|
||||
}
|
||||
) {
|
||||
Icon(FontAwesomeIcons.Regular.WindowClose, "close", Modifier.size(32.dp))
|
||||
@@ -54,5 +60,4 @@ fun <T> Toolbar(router: BackStack<T>, name: String, closable: Boolean, search: (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,10 +30,8 @@ import ca.gosyer.ui.base.components.LoadingScreen
|
||||
import ca.gosyer.ui.base.components.Pager
|
||||
import ca.gosyer.ui.base.components.PagerState
|
||||
import ca.gosyer.ui.base.vm.viewModel
|
||||
import ca.gosyer.ui.main.Routing
|
||||
import ca.gosyer.ui.manga.openMangaMenu
|
||||
import ca.gosyer.util.compose.ThemedWindow
|
||||
import com.github.zsoltk.compose.router.BackStack
|
||||
|
||||
fun openLibraryMenu() {
|
||||
ThemedWindow {
|
||||
|
||||
@@ -7,38 +7,11 @@
|
||||
package ca.gosyer.ui.main
|
||||
|
||||
import ca.gosyer.data.ui.UiPreferences
|
||||
import ca.gosyer.data.ui.model.Screen
|
||||
import ca.gosyer.ui.base.vm.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainViewModel @Inject constructor(
|
||||
uiPreferences: UiPreferences
|
||||
): ViewModel() {
|
||||
private val _menu = MutableStateFlow(
|
||||
uiPreferences.startScreen().get().toMenu()
|
||||
)
|
||||
val menu = _menu.asStateFlow()
|
||||
|
||||
fun switchMenu(menu: Menu) {
|
||||
scope.launch {
|
||||
_menu.value = menu
|
||||
}
|
||||
}
|
||||
|
||||
fun Screen.toMenu() = when (this) {
|
||||
Screen.Library -> Menu.Library
|
||||
Screen.Sources -> Menu.Sources
|
||||
Screen.Extensions -> Menu.Extensions
|
||||
}
|
||||
}
|
||||
|
||||
enum class Menu {
|
||||
Library,
|
||||
Sources,
|
||||
Extensions,
|
||||
Manga,
|
||||
Categories
|
||||
val startScreen = uiPreferences.startScreen().get()
|
||||
}
|
||||
@@ -67,9 +67,7 @@ fun MangaMenu(mangaId: Long, backStack: BackStack<Routing>? = null) {
|
||||
val serverUrl by vm.serverUrl.collectAsState()
|
||||
|
||||
Column(Modifier.background(MaterialTheme.colors.background)) {
|
||||
if (backStack != null) {
|
||||
Toolbar(backStack, "Manga", true)
|
||||
}
|
||||
Toolbar("Manga", backStack, backStack != null)
|
||||
|
||||
Surface(Modifier.height(40.dp).fillMaxWidth()) {
|
||||
Row {
|
||||
@@ -81,10 +79,10 @@ fun MangaMenu(mangaId: Long, backStack: BackStack<Routing>? = null) {
|
||||
manga?.let { manga ->
|
||||
Box {
|
||||
val state = rememberLazyListState()
|
||||
val items = remember(manga, chapters) {
|
||||
listOf(MangaMenu.MangaMenuManga(manga)) + chapters.map { MangaMenu.MangaMenuChapter(it) }
|
||||
}
|
||||
LazyColumn(state = state) {
|
||||
val items = remember(manga, chapters) {
|
||||
listOf(MangaMenu.MangaMenuManga(manga)) + chapters.map { MangaMenu.MangaMenuChapter(it) }
|
||||
}
|
||||
items(items) {
|
||||
when (it) {
|
||||
is MangaMenu.MangaMenuManga -> MangaItem(it.manga, serverUrl)
|
||||
|
||||
@@ -6,22 +6,34 @@
|
||||
|
||||
package ca.gosyer.ui.sources
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import ca.gosyer.data.models.Manga
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ca.gosyer.data.models.Source
|
||||
import ca.gosyer.ui.base.components.KtorImage
|
||||
import ca.gosyer.ui.base.components.Toolbar
|
||||
import ca.gosyer.ui.base.vm.viewModel
|
||||
import ca.gosyer.ui.manga.openMangaMenu
|
||||
import ca.gosyer.ui.sources.components.SourceHomeScreen
|
||||
import ca.gosyer.ui.sources.components.SourceScreen
|
||||
import ca.gosyer.ui.sources.components.SourceTopBar
|
||||
import ca.gosyer.util.compose.ThemedWindow
|
||||
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
||||
|
||||
fun openSourcesMenu() {
|
||||
ThemedWindow(title = "TachideskJUI - Sources") {
|
||||
@@ -40,22 +52,38 @@ fun SourcesMenu(onMangaClick: (Long) -> Unit) {
|
||||
val selectedSourceTab by vm.selectedSourceTab.collectAsState()
|
||||
val serverUrl by vm.serverUrl.collectAsState()
|
||||
|
||||
Column(Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
|
||||
SourceTopBar(
|
||||
openHome = {
|
||||
vm.selectTab(null)
|
||||
},
|
||||
sources = sourceTabs,
|
||||
tabSelected = selectedSourceTab,
|
||||
onTabSelected = vm::selectTab,
|
||||
onTabClosed = vm::closeTab
|
||||
)
|
||||
Surface {
|
||||
Column {
|
||||
Toolbar(selectedSourceTab?.name ?: "Sources", closable = false)
|
||||
Row {
|
||||
LazyColumn(Modifier.fillMaxHeight().width(64.dp)) {
|
||||
items(sourceTabs) { source ->
|
||||
Card(
|
||||
Modifier
|
||||
.clickable {
|
||||
vm.selectTab(source)
|
||||
}
|
||||
.requiredHeight(64.dp)
|
||||
.requiredWidth(64.dp),
|
||||
) {
|
||||
if (source != null) {
|
||||
KtorImage(source.iconUrl(serverUrl),)
|
||||
} else {
|
||||
Icon(Icons.Default.Home, "Home")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val selectedSource: Source? = selectedSourceTab
|
||||
if (selectedSource != null) {
|
||||
SourceScreen(selectedSource, onMangaClick)
|
||||
} else {
|
||||
SourceHomeScreen(isLoading, sources, serverUrl, vm::addTab)
|
||||
val selectedSource: Source? = selectedSourceTab
|
||||
BundleScope(selectedSource?.name ?: "home") {
|
||||
if (selectedSource != null) {
|
||||
SourceScreen(selectedSource, onMangaClick)
|
||||
} else {
|
||||
SourceHomeScreen(isLoading, sources, serverUrl, vm::addTab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class SourcesMenuViewModel @Inject constructor(
|
||||
private val _sources = MutableStateFlow(emptyList<Source>())
|
||||
val sources = _sources.asStateFlow()
|
||||
|
||||
private val _sourceTabs = MutableStateFlow(mapOf<Long?, Source?>(null to null))
|
||||
private val _sourceTabs = MutableStateFlow<List<Source?>>(listOf(null))
|
||||
val sourceTabs = _sourceTabs.asStateFlow()
|
||||
|
||||
private val _selectedSourceTab = MutableStateFlow<Source?>(null)
|
||||
@@ -65,12 +65,14 @@ class SourcesMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun addTab(source: Source) {
|
||||
_sourceTabs.value += source.id to source
|
||||
if (source !in _sourceTabs.value) {
|
||||
_sourceTabs.value += source
|
||||
}
|
||||
selectTab(source)
|
||||
}
|
||||
|
||||
fun closeTab(source: Source) {
|
||||
_sourceTabs.value -= source.id
|
||||
_sourceTabs.value -= source
|
||||
if (selectedSourceTab.value?.id == source.id) {
|
||||
_selectedSourceTab.value = null
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.ui.sources.components
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ScrollableTabRow
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ca.gosyer.data.models.Source
|
||||
|
||||
@Composable
|
||||
fun SourceTopBar(
|
||||
openHome: () -> Unit,
|
||||
sources: Map<Long?, Source?>,
|
||||
tabSelected: Source?,
|
||||
onTabSelected: (Source?) -> Unit,
|
||||
onTabClosed: (Source) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Surface(
|
||||
elevation = 2.dp,
|
||||
color = MaterialTheme.colors.surface
|
||||
) {
|
||||
SourceTabBar(
|
||||
modifier = modifier,
|
||||
onHomeClicked = openHome
|
||||
) { tabBarModifier ->
|
||||
SourceTabs(
|
||||
modifier = tabBarModifier,
|
||||
sources = sources,
|
||||
tabSelected = tabSelected,
|
||||
onTabSelected = { newTab -> onTabSelected(newTab) },
|
||||
onTabClosed = onTabClosed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceTabBar(
|
||||
modifier: Modifier = Modifier,
|
||||
onHomeClicked: () -> Unit,
|
||||
children: @Composable (Modifier) -> Unit
|
||||
) {
|
||||
Row(modifier) {
|
||||
// Separate Row as the children shouldn't have the padding
|
||||
Box(Modifier.padding(4.dp).align(Alignment.CenterVertically)) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onHomeClicked),
|
||||
imageVector = Icons.Filled.Home,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface)
|
||||
)
|
||||
}
|
||||
children(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SourceTabs(
|
||||
modifier: Modifier = Modifier,
|
||||
sources: Map<Long?, Source?>,
|
||||
tabSelected: Source?,
|
||||
onTabSelected: (Source?) -> Unit,
|
||||
onTabClosed: (Source) -> Unit
|
||||
) {
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = 1,//sources.indexOf(tabSelected).let { if (it == -1) 1 else it },
|
||||
modifier = modifier,
|
||||
contentColor = MaterialTheme.colors.onSurface,
|
||||
indicator = {
|
||||
|
||||
},
|
||||
divider = {
|
||||
|
||||
},
|
||||
backgroundColor = MaterialTheme.colors.surface
|
||||
) {
|
||||
sources.forEach { (sourceId, source) ->
|
||||
val selected = sourceId == tabSelected?.id
|
||||
|
||||
var rowModifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
if (selected) {
|
||||
rowModifier =
|
||||
Modifier
|
||||
.border(BorderStroke(2.dp, MaterialTheme.colors.onSurface), RectangleShape)
|
||||
.then(rowModifier)
|
||||
}
|
||||
|
||||
Tab(
|
||||
modifier = Modifier.background(MaterialTheme.colors.surface),
|
||||
selected = selected,
|
||||
onClick = { onTabSelected(source) }
|
||||
) {
|
||||
Row(rowModifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = source?.name?.toUpperCase() ?: "Sources",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (source != null) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onTabClosed(source)
|
||||
},
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user