Use side buttons instead of tabs for source browsing

This commit is contained in:
Syer10
2021-04-23 20:47:35 -04:00
parent 4f56520d5b
commit 35217d137d
9 changed files with 66 additions and 211 deletions

View File

@@ -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

View File

@@ -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()) {

View File

@@ -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: (
}
}
}
}

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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
}

View File

@@ -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)
)
}
}
}
}
}
}