Phase 5a: server settings UI — BookshelfSettings screen, model, navigation, strings
This commit is contained in:
parent
2f9431f6e6
commit
0c2dfbd592
8 changed files with 264 additions and 2 deletions
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.presentation.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.dueattendant149.bookshelf.data.settings.ServerSettings
|
||||
import org.dueattendant149.bookshelf.domain.model.remote.RemoteLibrary
|
||||
import org.dueattendant149.bookshelf.domain.use_case.remote.FetchLibrariesUseCase
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BookshelfSettingsState(
|
||||
val bookshelfUrl: String = "",
|
||||
val absUrl: String = "",
|
||||
val absToken: String = "",
|
||||
val libraries: List<RemoteLibrary> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
class BookshelfSettingsModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val serverSettings: ServerSettings,
|
||||
private val fetchLibrariesUseCase: FetchLibrariesUseCase,
|
||||
) : ViewModel() {
|
||||
private val _state = MutableStateFlow(BookshelfSettingsState())
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
load()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
viewModelScope.launch {
|
||||
_state.update {
|
||||
it.copy(
|
||||
bookshelfUrl = serverSettings.getBookshelfUrl() ?: "",
|
||||
absUrl = serverSettings.getAbsUrl() ?: "",
|
||||
absToken = serverSettings.getAbsToken() ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBookshelfUrl(value: String) = _state.update { it.copy(bookshelfUrl = value) }
|
||||
fun updateAbsUrl(value: String) = _state.update { it.copy(absUrl = value) }
|
||||
fun updateAbsToken(value: String) = _state.update { it.copy(absToken = value) }
|
||||
|
||||
fun save() {
|
||||
viewModelScope.launch {
|
||||
serverSettings.setBookshelfUrl(_state.value.bookshelfUrl)
|
||||
serverSettings.setAbsUrl(_state.value.absUrl)
|
||||
serverSettings.setAbsToken(_state.value.absToken)
|
||||
}
|
||||
}
|
||||
|
||||
fun testLibraries() {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isLoading = true, error = null) }
|
||||
save()
|
||||
val result = fetchLibrariesUseCase()
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
libraries = result.getOrDefault(emptyList()),
|
||||
error = result.exceptionOrNull()?.message,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.presentation.settings
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.dueattendant149.bookshelf.presentation.navigator.Screen
|
||||
import org.dueattendant149.bookshelf.ui.common.components.top_bar.collapsibleTopAppBarScrollBehavior
|
||||
import org.dueattendant149.bookshelf.ui.navigator.LocalNavigator
|
||||
import org.dueattendant149.bookshelf.ui.settings.bookshelf.BookshelfSettingsContent
|
||||
|
||||
@Parcelize
|
||||
object BookshelfSettingsScreen : Screen, Parcelable {
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.current
|
||||
val model = hiltViewModel<BookshelfSettingsModel>()
|
||||
val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior()
|
||||
|
||||
BookshelfSettingsContent(
|
||||
model = model,
|
||||
listState = listState,
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigateBack = {
|
||||
navigator.pop()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||
import androidx.compose.runtime.Composable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.dueattendant149.bookshelf.presentation.navigator.Screen
|
||||
import org.dueattendant149.bookshelf.presentation.settings.BookshelfSettingsScreen
|
||||
import org.dueattendant149.bookshelf.presentation.start.StartScreen
|
||||
import org.dueattendant149.bookshelf.ui.common.components.top_bar.collapsibleTopAppBarScrollBehavior
|
||||
import org.dueattendant149.bookshelf.ui.common.helpers.LocalSettings
|
||||
|
|
@ -46,6 +47,9 @@ object SettingsScreen : Screen, Parcelable {
|
|||
navigateToBrowseSettings = {
|
||||
navigator.push(BrowseSettingsScreen)
|
||||
},
|
||||
navigateToBookshelfSettings = {
|
||||
navigator.push(BookshelfSettingsScreen)
|
||||
},
|
||||
navigateToStart = {
|
||||
settings.showStartScreen.update(true)
|
||||
navigator.push(StartScreen, saveInBackStack = false)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ fun SettingsContent(
|
|||
navigateToReaderSettings: () -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit,
|
||||
navigateToBrowseSettings: () -> Unit,
|
||||
navigateToBookshelfSettings: () -> Unit,
|
||||
navigateToStart: () -> Unit,
|
||||
navigateBack: () -> Unit
|
||||
) {
|
||||
|
|
@ -32,6 +33,7 @@ fun SettingsContent(
|
|||
navigateToReaderSettings = navigateToReaderSettings,
|
||||
navigateToLibrarySettings = navigateToLibrarySettings,
|
||||
navigateToBrowseSettings = navigateToBrowseSettings,
|
||||
navigateToBookshelfSettings = navigateToBookshelfSettings,
|
||||
navigateToStart = navigateToStart,
|
||||
navigateBack = navigateBack
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Cloud
|
||||
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||
import androidx.compose.material.icons.outlined.DisplaySettings
|
||||
import androidx.compose.material.icons.outlined.Explore
|
||||
|
|
@ -31,7 +32,8 @@ fun SettingsLayout(
|
|||
navigateToAppearanceSettings: () -> Unit,
|
||||
navigateToReaderSettings: () -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit,
|
||||
navigateToBrowseSettings: () -> Unit
|
||||
navigateToBrowseSettings: () -> Unit,
|
||||
navigateToBookshelfSettings: () -> Unit
|
||||
) {
|
||||
LazyColumnWithScrollbar(
|
||||
Modifier
|
||||
|
|
@ -94,5 +96,16 @@ fun SettingsLayout(
|
|||
navigateToBrowseSettings()
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SettingsLayoutItem(
|
||||
index = 5,
|
||||
icon = Icons.Outlined.Cloud,
|
||||
title = stringResource(id = R.string.bookshelf_settings),
|
||||
description = stringResource(id = R.string.bookshelf_settings_desc)
|
||||
) {
|
||||
navigateToBookshelfSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ fun SettingsScaffold(
|
|||
navigateToReaderSettings: () -> Unit,
|
||||
navigateToLibrarySettings: () -> Unit,
|
||||
navigateToBrowseSettings: () -> Unit,
|
||||
navigateToBookshelfSettings: () -> Unit,
|
||||
navigateToStart: () -> Unit,
|
||||
navigateBack: () -> Unit
|
||||
) {
|
||||
|
|
@ -53,7 +54,8 @@ fun SettingsScaffold(
|
|||
navigateToAppearanceSettings = navigateToAppearanceSettings,
|
||||
navigateToReaderSettings = navigateToReaderSettings,
|
||||
navigateToLibrarySettings = navigateToLibrarySettings,
|
||||
navigateToBrowseSettings = navigateToBrowseSettings
|
||||
navigateToBrowseSettings = navigateToBrowseSettings,
|
||||
navigateToBookshelfSettings = navigateToBookshelfSettings
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.ui.settings.bookshelf
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.dueattendant149.bookshelf.presentation.settings.BookshelfSettingsModel
|
||||
import org.dueattendant149.bookshelf.ui.navigator.NavigatorBackIconButton
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookshelfSettingsContent(
|
||||
model: BookshelfSettingsModel,
|
||||
listState: LazyListState,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
navigateBack: () -> Unit,
|
||||
) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Bookshelf server") },
|
||||
navigationIcon = {
|
||||
NavigatorBackIconButton(navigateBack = navigateBack)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
item {
|
||||
OutlinedTextField(
|
||||
value = state.bookshelfUrl,
|
||||
onValueChange = model::updateBookshelfUrl,
|
||||
label = { Text("Bookshelf API URL") },
|
||||
placeholder = { Text("http://bookshelf-api:8073") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
item {
|
||||
OutlinedTextField(
|
||||
value = state.absUrl,
|
||||
onValueChange = model::updateAbsUrl,
|
||||
label = { Text("Audiobookshelf URL") },
|
||||
placeholder = { Text("http://192.168.1.119:13378") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
item {
|
||||
OutlinedTextField(
|
||||
value = state.absToken,
|
||||
onValueChange = model::updateAbsToken,
|
||||
label = { Text("ABS token") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
item {
|
||||
Button(
|
||||
onClick = model::testLibraries,
|
||||
enabled = !state.isLoading,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (state.isLoading) {
|
||||
CircularProgressIndicator()
|
||||
} else {
|
||||
Text("Fetch libraries")
|
||||
}
|
||||
}
|
||||
}
|
||||
state.error?.let { error ->
|
||||
item {
|
||||
Text(
|
||||
text = error,
|
||||
color = androidx.compose.material3.MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
items(state.libraries.size) { index ->
|
||||
val library = state.libraries[index]
|
||||
ListItem(
|
||||
headlineContent = { Text(library.name) },
|
||||
supportingContent = { Text("${library.mediaType} • ${library.itemCount} items") }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +134,8 @@
|
|||
<string name="browse_settings_desc">Scan, display</string>
|
||||
<string name="library_settings">Library</string>
|
||||
<string name="library_settings_desc">Categories, display, tabs</string>
|
||||
<string name="bookshelf_settings">Bookshelf</string>
|
||||
<string name="bookshelf_settings_desc">Server, ABS, TTS</string>
|
||||
|
||||
<!-- Settings subcategories -->
|
||||
<string name="font_reader_settings">Font</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue