diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/BookInfoScreen.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/BookInfoScreen.kt index f2dd2f4..45753b1 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/BookInfoScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/BookInfoScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -58,9 +57,7 @@ internal fun BookInfoScreen( Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back to reader") } }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), + colors = themedTopAppBarColors(), ) }, ) { diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ImageViewer.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ImageViewer.kt index 32f10a2..b12c669 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ImageViewer.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ImageViewer.kt @@ -24,7 +24,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -105,7 +104,7 @@ internal fun ImageViewer( contentWindowInsets = WindowInsets(0, 0, 0, 0), snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { - Surface(color = MaterialTheme.colorScheme.surface) { + ThemedTopBarSurface { Row( modifier = Modifier.fillMaxWidth().height(48.dp), verticalAlignment = Alignment.CenterVertically, diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/LibraryScreen.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/LibraryScreen.kt index b9025cd..afe486e 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/LibraryScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/LibraryScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close @@ -36,12 +37,9 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -53,6 +51,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -205,9 +204,7 @@ internal fun LibraryScreen( modifier = Modifier.fillMaxWidth(), ) }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), + colors = themedTopAppBarColors(), actions = { IconButton(onClick = { refresh() }, enabled = !busy && !loadingPage) { Icon(Icons.Filled.Refresh, contentDescription = "Refresh library") @@ -412,14 +409,13 @@ private fun LibrarySearchField( onClear: () -> Unit, modifier: Modifier = Modifier, ) { - val shape = RoundedCornerShape(18.dp) - OutlinedTextField( - value = value, - onValueChange = onValueChange, - singleLine = true, - textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface), + val shape = RoundedCornerShape(16.dp) + Row( modifier = modifier - .height(56.dp) + .height(42.dp) + .clip(shape) + .background(MaterialTheme.colorScheme.surface) + .padding(horizontal = 12.dp) .onPreviewKeyEvent { event -> if (event.type == KeyEventType.KeyDown && event.key == Key.Escape && value.isNotBlank()) { onClear() @@ -428,38 +424,43 @@ private fun LibrarySearchField( false } }, - placeholder = { - Text( - "Search library", - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.72f), - maxLines = 1, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + Icons.Filled.Search, + contentDescription = "Search library", + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.outline, + ) + Box( + modifier = Modifier + .weight(1f) + .padding(start = 8.dp, end = 6.dp), + contentAlignment = Alignment.CenterStart, + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + modifier = Modifier.fillMaxWidth(), ) - }, - leadingIcon = { - Icon( - Icons.Filled.Search, - contentDescription = "Search library", - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.outline, - ) - }, - trailingIcon = { - if (value.isNotBlank()) { - IconButton(onClick = onClear, modifier = Modifier.size(34.dp)) { - Icon(Icons.Filled.Close, contentDescription = "Clear search", modifier = Modifier.size(18.dp)) - } + if (value.isBlank()) { + Text( + "Search library", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.72f), + maxLines = 1, + ) } - }, - shape = shape, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = MaterialTheme.colorScheme.primary, - unfocusedBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.55f), - focusedContainerColor = MaterialTheme.colorScheme.surface, - unfocusedContainerColor = MaterialTheme.colorScheme.surface, - cursorColor = MaterialTheme.colorScheme.primary, - ), - ) + } + if (value.isNotBlank()) { + IconButton(onClick = onClear, modifier = Modifier.size(30.dp)) { + Icon(Icons.Filled.Close, contentDescription = "Clear search", modifier = Modifier.size(17.dp)) + } + } + } } @Composable diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderContent.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderContent.kt index 3f25f02..a4fac77 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderContent.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderContent.kt @@ -3,6 +3,10 @@ package net.sergeych.toread import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -30,6 +34,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -38,6 +43,9 @@ import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerType +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -67,6 +75,7 @@ import net.sergeych.toread.fb2.Fb2TextSpan import net.sergeych.toread.fb2.Fb2TextStyle import net.sergeych.toread.text.HyphenationRegistry import net.sergeych.toread.text.SoftHyphen +import kotlinx.coroutines.launch import kotlin.math.max import kotlin.math.min @@ -79,6 +88,7 @@ internal fun ContinuousBookReader( onImageOpen: (ViewedBookImage) -> Unit = {}, ) { val hyphenation = remember { HyphenationRegistry() } + val scope = rememberCoroutineScope() val contentPadding = if (isAndroidPlatform()) { PaddingValues(start = 6.dp, top = 6.dp, end = 0.dp, bottom = 6.dp) } else { @@ -88,7 +98,19 @@ internal fun ContinuousBookReader( LazyColumn( state = listState, modifier = modifier - .background(MaterialTheme.colorScheme.surface), + .background(MaterialTheme.colorScheme.surface) + .pageTurnOnTouchTap( + onPageDown = { + scope.launch { + listState.animateScrollBy(listState.pageScrollDistance()) + } + }, + onPageUp = { + scope.launch { + listState.animateScrollBy(-listState.pageScrollDistance()) + } + }, + ), contentPadding = contentPadding, verticalArrangement = Arrangement.spacedBy(10.dp), ) { @@ -116,6 +138,34 @@ internal fun ContinuousBookReader( } } +private fun Modifier.pageTurnOnTouchTap( + onPageDown: () -> Unit, + onPageUp: () -> Unit, +): Modifier = pointerInput(onPageDown, onPageUp) { + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Final) + if (down.type != PointerType.Touch) { + waitForUpOrCancellation(pass = PointerEventPass.Final) + return@awaitEachGesture + } + + val up = waitForUpOrCancellation(pass = PointerEventPass.Final) ?: return@awaitEachGesture + if (up.isConsumed) return@awaitEachGesture + + if (down.position.x < size.width / 2f) { + onPageDown() + } else { + onPageUp() + } + } +} + +private fun LazyListState.pageScrollDistance(): Float { + val layoutInfo = layoutInfo + val viewportHeight = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset + return viewportHeight.toFloat().coerceAtLeast(0f) +} + private fun LazyListScope.sectionItems( book: Fb2Book, section: Fb2Section, @@ -411,8 +461,9 @@ private fun ReaderText( @Composable private fun readerParagraphTextStyle(language: String?): TextStyle = MaterialTheme.typography.bodyLarge.copy( - fontSize = 18.sp, - lineHeight = 27.sp, + fontWeight = FontWeight(350), + fontSize = 21.sp, + lineHeight = 28.sp, hyphens = if (isAndroidPlatform()) Hyphens.Auto else Hyphens.Unspecified, lineBreak = if (isAndroidPlatform()) LineBreak.Paragraph else LineBreak.Unspecified, localeList = language?.takeIf(String::isNotBlank)?.let { LocaleList(Locale(it)) }, diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderScreen.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderScreen.kt index 2e16401..c0b1e97 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ReaderScreen.kt @@ -19,7 +19,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -142,7 +141,7 @@ private fun CompactReaderTopBar( onBookInfo: () -> Unit, onBack: () -> Unit, ) { - Surface(color = MaterialTheme.colorScheme.surface) { + ThemedTopBarSurface { Row( modifier = Modifier.fillMaxWidth().height(48.dp), verticalAlignment = Alignment.CenterVertically, diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ScanScreen.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ScanScreen.kt index 491d681..43459c0 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/ScanScreen.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/ScanScreen.kt @@ -28,7 +28,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -62,9 +61,7 @@ internal fun ScanScreen( Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back to library") } }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), + colors = themedTopAppBarColors(), ) }, ) { diff --git a/composeApp/src/commonMain/kotlin/net/sergeych/toread/SharedUi.kt b/composeApp/src/commonMain/kotlin/net/sergeych/toread/SharedUi.kt index 37a6dce..050b05f 100644 --- a/composeApp/src/commonMain/kotlin/net/sergeych/toread/SharedUi.kt +++ b/composeApp/src/commonMain/kotlin/net/sergeych/toread/SharedUi.kt @@ -3,16 +3,21 @@ package net.sergeych.toread import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -75,6 +80,30 @@ internal fun quietCardColors() = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.92f), ) +@Composable +internal fun ThemedTopBarSurface(content: @Composable ColumnScope.() -> Unit) { + Surface( + color = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ) { + Column { + content() + HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.38f)) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun themedTopAppBarColors(): TopAppBarColors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.primaryContainer, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer, + titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer, + actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ) + @Composable internal fun readerBackground(): Brush = SolidColor(MaterialTheme.colorScheme.background)