feat(ui): Phase 3 Myne-style Material You 3 redesign
- Add Shapes.kt with rounded shape scheme (8-32dp corners) wired into AppTheme. - BookshelfLibraryScreen: round cards (shapes.large), surfaceContainerLow, CenterAlignedTopAppBar, onSurfaceVariant labels, larger padding. - ServerSettingsScreen: section cards with round shapes, scrollable, CenterAlignedTopAppBar. - Dynamic color already enabled via AppTheme (Android 12+ dynamicColorScheme + materialkolor seed). - ':app:assembleOssDebug' passes.
This commit is contained in:
parent
d0ceeef24f
commit
1c0eca423d
4 changed files with 130 additions and 61 deletions
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.dueattendant149.bookreader.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Shapes
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Myne-style Material You 3 shape scheme: generous rounded corners.
|
||||||
|
*/
|
||||||
|
val AppShapes = Shapes(
|
||||||
|
extraSmall = RoundedCornerShape(8.dp),
|
||||||
|
small = RoundedCornerShape(12.dp),
|
||||||
|
medium = RoundedCornerShape(16.dp),
|
||||||
|
large = RoundedCornerShape(24.dp),
|
||||||
|
extraLarge = RoundedCornerShape(32.dp),
|
||||||
|
)
|
||||||
|
|
@ -178,6 +178,7 @@ fun AppTheme(
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = finalColorScheme,
|
colorScheme = finalColorScheme,
|
||||||
typography = appFontFamily?.let { AppTypography.withAppFontFamily(it) } ?: AppTypography,
|
typography = appFontFamily?.let { AppTypography.withAppFontFamily(it) } ?: AppTypography,
|
||||||
|
shapes = AppShapes,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
package org.dueattendant149.bookreader.bookshelf
|
package org.dueattendant149.bookreader.bookshelf
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -23,7 +28,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
|
||||||
|
|
@ -34,13 +38,13 @@ fun ServerSettingsScreen(
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
var bookshelfUrl by remember(uiState.bookshelfUrl) { androidx.compose.runtime.mutableStateOf(uiState.bookshelfUrl) }
|
var bookshelfUrl by remember(uiState.bookshelfUrl) { mutableStateOf(uiState.bookshelfUrl) }
|
||||||
var absUrl by remember(uiState.absUrl) { androidx.compose.runtime.mutableStateOf(uiState.absUrl) }
|
var absUrl by remember(uiState.absUrl) { mutableStateOf(uiState.absUrl) }
|
||||||
var absToken by remember(uiState.absToken) { androidx.compose.runtime.mutableStateOf(uiState.absToken) }
|
var absToken by remember(uiState.absToken) { mutableStateOf(uiState.absToken) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
androidx.compose.material3.CenterAlignedTopAppBar(
|
||||||
title = { Text("Server settings") },
|
title = { Text("Server settings") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBackClick) {
|
IconButton(onClick = onBackClick) {
|
||||||
|
|
@ -54,61 +58,87 @@ fun ServerSettingsScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.padding(16.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text("Bookshelf API")
|
SettingsSectionCard(title = "Bookshelf API") {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
OutlinedTextField(
|
||||||
OutlinedTextField(
|
value = bookshelfUrl,
|
||||||
value = bookshelfUrl,
|
onValueChange = {
|
||||||
onValueChange = {
|
bookshelfUrl = it
|
||||||
bookshelfUrl = it
|
viewModel.updateBookshelfUrl(it)
|
||||||
viewModel.updateBookshelfUrl(it)
|
},
|
||||||
},
|
label = { Text("https://books.dueattendant149.org/") },
|
||||||
label = { Text("https://books.dueattendant149.org/") },
|
singleLine = true,
|
||||||
singleLine = true,
|
modifier = Modifier.fillMaxWidth()
|
||||||
modifier = Modifier.fillMaxWidth()
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
SettingsSectionCard(title = "Audiobookshelf") {
|
||||||
|
OutlinedTextField(
|
||||||
Text("Audiobookshelf")
|
value = absUrl,
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
onValueChange = {
|
||||||
OutlinedTextField(
|
absUrl = it
|
||||||
value = absUrl,
|
viewModel.updateAbsUrl(it)
|
||||||
onValueChange = {
|
},
|
||||||
absUrl = it
|
label = { Text("https://abs.dueattendant149.org/") },
|
||||||
viewModel.updateAbsUrl(it)
|
singleLine = true,
|
||||||
},
|
modifier = Modifier.fillMaxWidth()
|
||||||
label = { Text("https://abs.dueattendant149.org/") },
|
)
|
||||||
singleLine = true,
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
modifier = Modifier.fillMaxWidth()
|
OutlinedTextField(
|
||||||
)
|
value = absToken,
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
onValueChange = {
|
||||||
OutlinedTextField(
|
absToken = it
|
||||||
value = absToken,
|
viewModel.updateAbsToken(it)
|
||||||
onValueChange = {
|
},
|
||||||
absToken = it
|
label = { Text("Bearer token") },
|
||||||
viewModel.updateAbsToken(it)
|
singleLine = true,
|
||||||
},
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
label = { Text("Bearer token") },
|
modifier = Modifier.fillMaxWidth()
|
||||||
singleLine = true,
|
)
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
}
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = viewModel::save,
|
onClick = viewModel::save,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = MaterialTheme.shapes.large
|
||||||
) {
|
) {
|
||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiState.saved) {
|
if (uiState.saved) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Text(
|
||||||
Text("Saved")
|
"Saved",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SettingsSectionCard(
|
||||||
|
title: String,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(20.dp)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(bottom = 12.dp)
|
||||||
|
)
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -56,7 +55,7 @@ fun BookshelfLibraryScreen(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
androidx.compose.material3.CenterAlignedTopAppBar(
|
||||||
title = { Text("Server Library") },
|
title = { Text("Server Library") },
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onOpenSettings) {
|
IconButton(onClick = onOpenSettings) {
|
||||||
|
|
@ -145,9 +144,13 @@ private fun LibraryCard(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 6.dp),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
colors = androidx.compose.material3.CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(20.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = library.name,
|
text = library.name,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
|
@ -156,7 +159,9 @@ private fun LibraryCard(
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "${library.itemCount} items · ${library.mediaType}",
|
text = "${library.itemCount} items · ${library.mediaType}",
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,9 +177,13 @@ private fun ItemCard(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 6.dp),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
colors = androidx.compose.material3.CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(20.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = item.title,
|
text = item.title,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
|
@ -184,16 +193,29 @@ private fun ItemCard(
|
||||||
if (item.author.isNotBlank()) {
|
if (item.author.isNotBlank()) {
|
||||||
Text(
|
Text(
|
||||||
text = item.author,
|
text = item.author,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = item.type,
|
text = item.type,
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
)
|
)
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(top = 8.dp))
|
LinearProgressIndicator(
|
||||||
Text("Downloading...", style = MaterialTheme.typography.bodySmall)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 12.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Downloading...",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue