From 0c2dfbd5924c4e7f343691c03c2e7e4395a0b5e3 Mon Sep 17 00:00:00 2001 From: Atte149 Date: Tue, 16 Jun 2026 09:45:17 +0300 Subject: [PATCH] =?UTF-8?q?Phase=205a:=20server=20settings=20UI=20?= =?UTF-8?q?=E2=80=94=20BookshelfSettings=20screen,=20model,=20navigation,?= =?UTF-8?q?=20strings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/BookshelfSettingsModel.kt | 82 ++++++++++++ .../settings/BookshelfSettingsScreen.kt | 39 ++++++ .../presentation/settings/SettingsScreen.kt | 4 + .../bookshelf/ui/settings/SettingsContent.kt | 2 + .../bookshelf/ui/settings/SettingsLayout.kt | 15 ++- .../bookshelf/ui/settings/SettingsScaffold.kt | 4 +- .../bookshelf/BookshelfSettingsContent.kt | 118 ++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 8 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt create mode 100644 app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsScreen.kt create mode 100644 app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt diff --git a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt new file mode 100644 index 00000000..19bef6b7 --- /dev/null +++ b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt @@ -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 = 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, + ) + } + } + } + } diff --git a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsScreen.kt b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsScreen.kt new file mode 100644 index 00000000..92516eea --- /dev/null +++ b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsScreen.kt @@ -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() + val (scrollBehavior, listState) = TopAppBarDefaults.collapsibleTopAppBarScrollBehavior() + + BookshelfSettingsContent( + model = model, + listState = listState, + scrollBehavior = scrollBehavior, + navigateBack = { + navigator.pop() + } + ) + } +} diff --git a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/SettingsScreen.kt b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/SettingsScreen.kt index bcbd249a..8fd8afcf 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/SettingsScreen.kt @@ -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) diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsContent.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsContent.kt index 7e36cbb4..f8a2e12f 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsContent.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsContent.kt @@ -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 ) diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsLayout.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsLayout.kt index 0b1c3142..e0660c15 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsLayout.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsLayout.kt @@ -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() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsScaffold.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsScaffold.kt index 7b44d958..015a9164 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsScaffold.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/SettingsScaffold.kt @@ -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 ) } } \ No newline at end of file diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt new file mode 100644 index 00000000..185befb5 --- /dev/null +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt @@ -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") } + ) + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4663610..64a30bfe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -134,6 +134,8 @@ Scan, display Library Categories, display, tabs + Bookshelf + Server, ABS, TTS Font