diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b589d56..b86273d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1832297..cdecf77 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,3 @@
-
import java.net.InetAddress
import java.text.SimpleDateFormat
import java.util.Date
@@ -22,7 +21,7 @@ android {
minSdk = 24
targetSdk = 34
// 版本号为x.y.z则versionCode为x*1000000+y*10000+z*100+debug版本号(开发需要时迭代, 两位数)
- versionCode = 1_00_00_006
+ versionCode = 1_00_00_010
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/book/content/ContentScreen.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/book/content/ContentScreen.kt
index be9ebc9..a91f7a0 100644
--- a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/book/content/ContentScreen.kt
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/book/content/ContentScreen.kt
@@ -491,7 +491,7 @@ fun SettingsBottomSheet(
) {
item {
SettingsSliderEntry(
- description = "阅读器字体大小",
+ title = "阅读器字体大小",
unit = "sp",
valueRange = 8f..64f,
value = settingState.fontSize,
@@ -500,7 +500,7 @@ fun SettingsBottomSheet(
}
item {
SettingsSliderEntry(
- description = "阅读器行距大小",
+ title = "阅读器行距大小",
unit = "sp",
valueRange = 0f..32f,
value = settingState.fontLineHeight,
@@ -610,7 +610,7 @@ fun SettingsBottomSheet(
if(!settingState.autoPadding) {
item {
SettingsSliderEntry(
- description = "上边距",
+ title = "上边距",
unit = "dp",
valueRange = 0f..128f,
value = settingState.topPadding,
@@ -621,7 +621,7 @@ fun SettingsBottomSheet(
if(!settingState.autoPadding) {
item {
SettingsSliderEntry(
- description = "下边距",
+ title = "下边距",
unit = "dp",
valueRange = 0f..128f,
value = settingState.bottomPadding,
@@ -632,7 +632,7 @@ fun SettingsBottomSheet(
if(!settingState.autoPadding) {
item {
SettingsSliderEntry(
- description = "左边距",
+ title = "左边距",
unit = "dp",
valueRange = 0f..128f,
value = settingState.leftPadding,
@@ -643,7 +643,7 @@ fun SettingsBottomSheet(
if(!settingState.autoPadding) {
item {
SettingsSliderEntry(
- description = "右边距",
+ title = "右边距",
unit = "dp",
valueRange = 0f..128f,
value = settingState.rightPadding,
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/components/SettingsEntry.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/components/SettingsEntry.kt
index dd7e0c0..9db0c6b 100644
--- a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/components/SettingsEntry.kt
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/components/SettingsEntry.kt
@@ -2,15 +2,24 @@ package indi.dmzz_yyhyy.lightnovelreader.ui.components
import android.content.Intent
import android.net.Uri
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
@@ -23,9 +32,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity
@@ -38,6 +52,7 @@ import kotlin.math.roundToInt
@Composable
fun SettingsSwitchEntry(
modifier: Modifier = Modifier,
+ iconRes: Int = -1,
title: String,
description: String,
checked: Boolean,
@@ -46,6 +61,7 @@ fun SettingsSwitchEntry(
) {
SettingsSwitchEntry(
modifier = modifier,
+ iconRes = iconRes,
title = title,
description = description,
checked = checked,
@@ -57,46 +73,69 @@ fun SettingsSwitchEntry(
@Composable
fun SettingsSwitchEntry(
modifier: Modifier = Modifier,
+ iconRes: Int,
title: String,
description: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
disabled: Boolean = false
) {
- FilledCard(
+ Row(
modifier = modifier
- .fillMaxWidth(),
- shape = RoundedCornerShape(6.dp)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(6.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ .wrapContentHeight()
+ .then(if (!disabled) Modifier.clickable { onCheckedChange(!checked) } else Modifier)
+ .padding(start = 18.dp, end = 14.dp)
+ .padding(vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .then(if (disabled) Modifier.clickable {} else Modifier.clickable { onCheckedChange(!checked) })
- .padding(18.dp, 10.dp, 20.dp, 12.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Column(
- Modifier
- .weight(2f)
- .padding(end = 4.dp)
+ if (iconRes > 0) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .padding(end = 8.dp),
+ contentAlignment = Alignment.Center
) {
- Text(
- text = title,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 18.sp,
- color = MaterialTheme.colorScheme.onSurface,
- maxLines = 1
- )
- Text(
- text = description,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.onSurface
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(iconRes),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ contentDescription = "Icon"
)
}
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 4.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.Start
+ ) {
+ Text(
+ text = title,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = 16.sp,
+ lineHeight = 16.sp
+ )
+ Spacer(modifier = Modifier.height(2.dp))
+ Text(
+ text = description,
+ color = MaterialTheme.colorScheme.secondary,
+ fontSize = 14.sp,
+ lineHeight = 18.sp
+ )
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxHeight(),
+ contentAlignment = Alignment.Center
+ ) {
Switch(
checked = checked,
enabled = !disabled,
@@ -108,8 +147,9 @@ fun SettingsSwitchEntry(
@Composable
fun SettingsSliderEntry(
+ iconRes: Int = -1,
modifier: Modifier = Modifier,
- description: String,
+ title: String,
unit: String,
value: Float,
valueRange: ClosedFloatingPointRange = 0f..1f,
@@ -120,8 +160,9 @@ fun SettingsSliderEntry(
tempValue = value
}
SettingsSliderEntry(
+ iconRes = iconRes,
modifier = modifier,
- description = description,
+ title = title,
unit = unit,
value = tempValue,
valueRange = valueRange,
@@ -131,35 +172,63 @@ fun SettingsSliderEntry(
}
@Composable
-fun SettingsSliderEntry(
+private fun SettingsSliderEntry(
modifier: Modifier = Modifier,
- description: String,
+ iconRes: Int,
+ title: String,
unit: String,
value: Float,
valueRange: ClosedFloatingPointRange = 0f..1f,
onSlideChange: (Float) -> Unit,
onSliderChangeFinished: () -> Unit
) {
- FilledCard(
- modifier = modifier,
- shape = RoundedCornerShape(6.dp)
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(6.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ .wrapContentHeight()
+ .padding(start = 18.dp, end = 14.dp)
+ .padding(vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
- Column(Modifier.padding(18.dp, 10.dp, 20.dp, 12.dp)) {
+ if (iconRes > 0) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .padding(end = 8.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(iconRes),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ contentDescription = "Icon"
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 4.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.Start
+ ) {
Text(
- text = description,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 18.sp,
+ text = title,
color = MaterialTheme.colorScheme.onSurface,
- maxLines = 1
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = 16.sp,
+ lineHeight = 16.sp
)
+ Spacer(modifier = Modifier.height(2.dp))
Text(
text = "${DecimalFormat("#.#").format(value)}$unit",
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.secondary,
+ color = MaterialTheme.colorScheme.primary,
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
maxLines = 1
)
Slider(
@@ -176,54 +245,84 @@ fun SettingsSliderEntry(
}
}
+@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SettingsMenuEntry(
+ iconRes: Int = -1,
title: String,
- description: String,
+ description: String? = null,
options: MenuOptions,
selectedOptionKey: String,
onOptionChange: (String) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
+ var offset by remember { mutableStateOf(Offset.Zero) }
- FilledCard(
+ Row(
modifier = Modifier
- .fillMaxWidth(),
- shape = RoundedCornerShape(6.dp)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(6.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ .wrapContentHeight()
+ .pointerInteropFilter {
+ offset = Offset(it.x, it.y); false
+ }
+ .clickable { expanded = !expanded }
+ .padding(start = 18.dp, end = 14.dp)
+ .padding(vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
- Box(modifier = Modifier.clickable { expanded = !expanded }) {
- Row(
+ if (iconRes > 0) {
+ Box(
modifier = Modifier
- .fillMaxWidth()
- .padding(18.dp, 10.dp, 20.dp, 12.dp),
- verticalAlignment = Alignment.CenterVertically
+ .fillMaxHeight()
+ .padding(end = 8.dp),
+ contentAlignment = Alignment.Center
) {
- Column(
- Modifier.weight(2f)
- ) {
- Text(
- text = title,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 18.sp,
- color = MaterialTheme.colorScheme.onSurface,
- maxLines = 1
- )
- Column {
- Text(
- text = description,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.onSurface
- )
- AnimatedText(
- text = options.get(selectedOptionKey).name,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.primary
- )
- }
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(iconRes),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ contentDescription = "Icon"
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 4.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.Start
+ ) {
+ Text(
+ text = title,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = 16.sp,
+ lineHeight = 16.sp
+ )
+ Spacer(modifier = Modifier.height(2.dp))
+ description?.let {
+ Text(
+ text = description,
+ color = MaterialTheme.colorScheme.secondary,
+ fontSize = 14.sp,
+ lineHeight = 18.sp
+ )
+ }
+ AnimatedText(
+ text = options.get(selectedOptionKey).name,
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
+ color = MaterialTheme.colorScheme.primary
+ )
+ Box(
+ modifier = Modifier.offset {
+ IntOffset(offset.x.toInt(), 0)
}
+ ) {
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
@@ -234,23 +333,24 @@ fun SettingsMenuEntry(
onOptionChange(option.key)
expanded = false
},
- text = { Text(option.name) },
+ text = { Text(option.name, fontSize = 14.sp) },
)
}
}
}
}
-
}
}
@Composable
fun SettingsClickableEntry(
+ iconRes: Int = -1,
title: String,
description: String,
option: String? = null
) {
SettingsClickableEntry(
+ iconRes = iconRes,
title = title,
description = description,
option = option,
@@ -260,6 +360,7 @@ fun SettingsClickableEntry(
@Composable
fun SettingsClickableEntry(
+ iconRes: Int = -1,
title: String,
description: String,
option: String? = null,
@@ -267,6 +368,7 @@ fun SettingsClickableEntry(
) {
val context = LocalContext.current
SettingsClickableEntry(
+ iconRes = iconRes,
title = title,
description = description,
option = option,
@@ -281,59 +383,61 @@ fun SettingsClickableEntry(
@Composable
fun SettingsClickableEntry(
+ iconRes: Int = -1,
title: String,
description: String,
option: String? = null,
onClick: () -> Unit
) {
- var expanded by remember { mutableStateOf(false) }
-
- FilledCard(
+ Row(
modifier = Modifier
- .fillMaxWidth(),
- shape = RoundedCornerShape(6.dp)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(6.dp))
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ .wrapContentHeight()
+ .clickable { onClick.invoke() }
+ .padding(start = 18.dp, end = 14.dp)
+ .padding(vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
- Box(
- modifier = Modifier.clickable {
- expanded = !expanded
- onClick.invoke()
- }
- ) {
- Row(
+ if (iconRes > 0) {
+ Box(
modifier = Modifier
- .fillMaxWidth()
- .padding(18.dp, 10.dp, 20.dp, 12.dp),
- verticalAlignment = Alignment.CenterVertically
+ .fillMaxHeight()
+ .padding(end = 8.dp),
+ contentAlignment = Alignment.Center
) {
- Column(
- Modifier.weight(2f)
- ) {
- Text(
- text = title,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.W500,
- fontSize = 18.sp,
- color = MaterialTheme.colorScheme.onSurface,
- maxLines = 1
- )
- Column {
- Text(
- text = description,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.onSurface
- )
- option?.let {
- Text(
- text = it,
- fontSize = 13.sp,
- lineHeight = 14.sp,
- color = MaterialTheme.colorScheme.primary
- )
- }
- }
- }
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(iconRes),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ contentDescription = "Icon"
+ )
}
}
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 4.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.Start
+ ) {
+ Text(
+ text = title,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = 16.sp,
+ lineHeight = 16.sp
+ )
+ Spacer(modifier = Modifier.height(2.dp))
+ Text(
+ text = description,
+ color = MaterialTheme.colorScheme.secondary,
+ fontSize = 14.sp,
+ lineHeight = 18.sp
+ )
+ }
}
}
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfHomeScreen.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfHomeScreen.kt
index 10582ba..ca81afd 100644
--- a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfHomeScreen.kt
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfHomeScreen.kt
@@ -13,14 +13,10 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
-import androidx.compose.foundation.combinedClickable
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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
@@ -62,21 +58,19 @@ 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.draw.drawBehind
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -90,11 +84,9 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.workDataOf
import indi.dmzz_yyhyy.lightnovelreader.R
-import indi.dmzz_yyhyy.lightnovelreader.data.book.BookInformation
import indi.dmzz_yyhyy.lightnovelreader.data.work.SaveBookshelfWork
import indi.dmzz_yyhyy.lightnovelreader.ui.components.AddBookToBookshelfDialog
import indi.dmzz_yyhyy.lightnovelreader.ui.components.AnimatedText
-import indi.dmzz_yyhyy.lightnovelreader.ui.components.Cover
import indi.dmzz_yyhyy.lightnovelreader.ui.components.EmptyPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -125,6 +117,7 @@ fun BookshelfHomeScreen(
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
+ val haptic = LocalHapticFeedback.current
val workManager = WorkManager.getInstance(context)
val enterAlwaysScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val animatedBackgroundColor by animateColorAsState(
@@ -136,15 +129,16 @@ fun BookshelfHomeScreen(
val importBookshelfLauncher = launcher(importBookshelf)
val lazyListState = rememberLazyListState()
var visibleBookshelfSelectDialog by remember { mutableStateOf(false) }
- val dialogSelectedBooksheves = remember { mutableStateListOf() }
- var updatedBooksExpended by remember { mutableStateOf(true) }
- var pinnedBooksExpended by remember { mutableStateOf(true) }
- var allBooksExpended by remember { mutableStateOf(true) }
+ val dialogSelectedBookshelves = remember { mutableStateListOf() }
+ var updatedBooksExpanded by remember { mutableStateOf(true) }
+ var pinnedBooksExpanded by remember { mutableStateOf(true) }
+ var allBooksExpanded by remember { mutableStateOf(true) }
topBar {
TopBar(
scrollBehavior = enterAlwaysScrollBehavior,
backgroundColor = animatedBackgroundColor,
selectMode = uiState.selectMode,
+ uiState = uiState,
onClickCreate = onClickCreate,
onClickSearch = {},
onClickEdit = { onClickEdit(uiState.selectedBookshelfId) },
@@ -195,7 +189,7 @@ fun BookshelfHomeScreen(
)
}
LaunchedEffect(visibleBookshelfSelectDialog) {
- dialogSelectedBooksheves.clear()
+ dialogSelectedBookshelves.clear()
}
dialog {
if (visibleBookshelfSelectDialog)
@@ -203,14 +197,14 @@ fun BookshelfHomeScreen(
onDismissRequest = { visibleBookshelfSelectDialog = false },
onConfirmation = {
scope.launch {
- markSelectedBooks(dialogSelectedBooksheves)
+ markSelectedBooks(dialogSelectedBookshelves)
visibleBookshelfSelectDialog = false
}
},
- onSelectBookshelf = dialogSelectedBooksheves::add,
- onDeselectBookshelf = dialogSelectedBooksheves::remove,
+ onSelectBookshelf = dialogSelectedBookshelves::add,
+ onDeselectBookshelf = dialogSelectedBookshelves::remove,
allBookshelf = uiState.bookshelfList,
- selectedBookshelfIds = dialogSelectedBooksheves
+ selectedBookshelfIds = dialogSelectedBookshelves
)
}
LifecycleEventEffect(Lifecycle.Event.ON_START) {
@@ -222,15 +216,10 @@ fun BookshelfHomeScreen(
clearToast()
}
Column(
- modifier = Modifier
- .fillMaxSize()
- .drawBehind {
- drawRect(animatedBackgroundColor)
- }
+ modifier = Modifier.fillMaxSize()
) {
if (uiState.bookshelfList.size > 4) {
ScrollableTabRow(
- containerColor = animatedBackgroundColor,
selectedTabIndex = uiState.selectedTabIndex,
edgePadding = 16.dp,
indicator = { tabPositions ->
@@ -260,8 +249,7 @@ fun BookshelfHomeScreen(
}
else {
PrimaryTabRow(
- selectedTabIndex = uiState.selectedTabIndex,
- containerColor = animatedBackgroundColor
+ selectedTabIndex = uiState.selectedTabIndex
) {
uiState.bookshelfList.forEach { bookshelf ->
Tab(
@@ -284,6 +272,13 @@ fun BookshelfHomeScreen(
description = "单击“收藏”按钮,将书本加入此书架"
)
}
+
+ val onLongPress: (Int) -> Unit = { bookId ->
+ onClickEnableSelectMode.invoke()
+ changeBookSelectState(bookId)
+ haptic.performHapticFeedback(HapticFeedbackType.LongPress)
+ }
+
LazyColumn(
modifier = Modifier
.fillMaxWidth()
@@ -298,20 +293,25 @@ fun BookshelfHomeScreen(
modifier = Modifier.animateItem(),
icon = painterResource(R.drawable.keep_24px),
title = "已更新 (${uiState.selectedBookshelf.updatedBookIds.size})",
- expanded = updatedBooksExpended,
- onClickExpand = { updatedBooksExpended = !updatedBooksExpended }
+ expanded = updatedBooksExpanded,
+ onClickExpand = { updatedBooksExpanded = !updatedBooksExpanded }
)
}
- if (updatedBooksExpended && !uiState.selectMode) {
+ if (updatedBooksExpanded && !uiState.selectMode) {
items(uiState.selectedBookshelf.updatedBookIds.reversed()) { updatedBookId ->
uiState.bookInformationMap[updatedBookId]?.let {
- UpdatedBookRow(
- modifier = Modifier.animateItem(),
+ BookCardItem(
bookInformation = it,
- lastChapterTitle = uiState.bookLastChapterTitleMap[updatedBookId] ?: "",
+ haptic = haptic,
selected = uiState.selectedBookIds.contains(it.id),
- onClick = { onClickBook(it.id) },
- onLongPress = {}
+ latestChapterTitle = uiState.bookLastChapterTitleMap[updatedBookId],
+ onClick = {
+ if (!uiState.selectMode)
+ onClickBook(it.id)
+ else changeBookSelectState(it.id)
+ },
+ onLongPress = { onLongPress(it.id) },
+ progress = {}
)
}
}
@@ -322,26 +322,24 @@ fun BookshelfHomeScreen(
modifier = Modifier.animateItem(),
icon = painterResource(R.drawable.keep_24px),
title = "已固定 (${uiState.selectedBookshelf.pinnedBookIds.size})",
- expanded = pinnedBooksExpended,
- onClickExpand = { pinnedBooksExpended = !pinnedBooksExpended }
+ expanded = pinnedBooksExpanded,
+ onClickExpand = { pinnedBooksExpanded = !pinnedBooksExpanded }
)
}
- if (pinnedBooksExpended) {
+ if (pinnedBooksExpanded) {
items(uiState.selectedBookshelf.pinnedBookIds.reversed()) { pinnedBookId ->
- uiState.bookInformationMap[pinnedBookId]?.let { bookInformation ->
- BookRow(
- modifier = Modifier.animateItem(),
- bookInformation = bookInformation,
- selected = uiState.selectedBookIds.contains(bookInformation.id),
+ uiState.bookInformationMap[pinnedBookId]?.let {
+ BookCardItem(
+ bookInformation = it,
+ haptic = haptic,
+ selected = uiState.selectedBookIds.contains(it.id),
onClick = {
if (!uiState.selectMode)
- onClickBook(bookInformation.id)
- else changeBookSelectState(bookInformation.id)
+ onClickBook(it.id)
+ else changeBookSelectState(it.id)
},
- onLongPress = {
- onClickEnableSelectMode.invoke()
- changeBookSelectState(bookInformation.id)
- }
+ onLongPress = { onLongPress(it.id) },
+ progress = {}
)
}
}
@@ -352,28 +350,24 @@ fun BookshelfHomeScreen(
modifier = Modifier.animateItem(),
icon = painterResource(R.drawable.outline_bookmark_24px),
title = "全部 (${uiState.selectedBookshelf.allBookIds.size})",
- expanded = allBooksExpended,
- onClickExpand = { allBooksExpended = !allBooksExpended }
+ expanded = allBooksExpanded,
+ onClickExpand = { allBooksExpanded = !allBooksExpanded }
)
}
- if (allBooksExpended) {
- items(
- uiState.selectedBookshelf.allBookIds.reversed(),
- ) { bookId ->
+ if (allBooksExpanded) {
+ items(uiState.selectedBookshelf.allBookIds.reversed()) { bookId ->
uiState.bookInformationMap[bookId]?.let {
- BookRow(
- modifier = Modifier.animateItem(),
+ BookCardItem(
bookInformation = it,
+ haptic = haptic,
selected = uiState.selectedBookIds.contains(it.id),
onClick = {
if (!uiState.selectMode)
onClickBook(it.id)
else changeBookSelectState(it.id)
},
- onLongPress = {
- onClickEnableSelectMode.invoke()
- changeBookSelectState(it.id)
- }
+ onLongPress = { onLongPress(it.id) },
+ progress = {}
)
}
}
@@ -422,231 +416,13 @@ fun CollapseGroupTitle(
}
}
-@Composable
-fun BookRow(
- modifier: Modifier = Modifier,
- bookInformation: BookInformation,
- selected: Boolean,
- onClick: () -> Unit,
- onLongPress: () -> Unit
-) {
- val descriptionTextStyle = MaterialTheme.typography.labelLarge.copy(
- fontSize = 13.sp,
- lineHeight = 12.5.sp,
- fontWeight = FontWeight.W400
- )
- BasicBookRow(
- modifier = modifier,
- bookInformation = bookInformation,
- selected = selected,
- onClick = onClick,
- onLongPress = onLongPress
- ) {
- Text(
- text = buildAnnotatedString {
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append(bookInformation.author)
- }
- withStyle(
- style = descriptionTextStyle.copy(fontWeight = FontWeight.W900).toSpanStyle()
- ) {
- append(" · ")
- }
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append(bookInformation.publishingHouse)
- }
- },
- style = descriptionTextStyle,
- maxLines = 1
- )
- Text(
- text = buildAnnotatedString {
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append("${bookInformation.wordCount / 1000}K 字")
- }
- withStyle(style = SpanStyle(fontWeight = FontWeight.W900)) {
- append(" · ")
- }
- if (!bookInformation.isComplete)
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append("更新: ${bookInformation.lastUpdated.year}-${bookInformation.lastUpdated.monthValue}-${bookInformation.lastUpdated.dayOfMonth}")
- }
- else
- withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
- append("已完结")
- }
- },
- style = descriptionTextStyle,
- maxLines = 1
- )
- Text(
- text = bookInformation.tags.joinToString(" "),
- style = descriptionTextStyle,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- Text(
- text = bookInformation.description.trim(),
- style = descriptionTextStyle,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
-}
-
-@Composable
-fun UpdatedBookRow(
- modifier: Modifier = Modifier,
- bookInformation: BookInformation,
- lastChapterTitle: String,
- selected: Boolean,
- onClick: () -> Unit,
- onLongPress: () -> Unit
-) {
- val descriptionTextStyle = MaterialTheme.typography.labelLarge.copy(
- fontSize = 12.sp,
- lineHeight = 12.5.sp,
- fontWeight = FontWeight.W400
- )
- val primary = MaterialTheme.colorScheme.primary
- BasicBookRow(
- modifier = modifier,
- bookInformation = bookInformation,
- selected = selected,
- onClick = onClick,
- onLongPress = onLongPress
- ) {
- Text(
- text = buildAnnotatedString {
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append(bookInformation.author)
- }
- withStyle(
- style = descriptionTextStyle.copy(fontWeight = FontWeight.W900).toSpanStyle()
- ) {
- append(" · ")
- }
- withStyle(descriptionTextStyle.toSpanStyle()) {
- append(bookInformation.publishingHouse)
- }
- },
- style = descriptionTextStyle,
- maxLines = 1
- )
- Text(
- text = buildAnnotatedString {
- withStyle(
- style = descriptionTextStyle.copy(fontWeight = FontWeight.W900).toSpanStyle()
- ) {
- append("更新至 ")
- }
- withStyle(
- style = descriptionTextStyle.copy(fontWeight = FontWeight.W900, color = primary).toSpanStyle()
- ) {
- append(lastChapterTitle)
- }
- },
- style = descriptionTextStyle,
- maxLines = 1
- )
- Text(
- text = bookInformation.tags.joinToString(" "),
- style = descriptionTextStyle,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun BasicBookRow(
- modifier: Modifier = Modifier,
- bookInformation: BookInformation,
- selected: Boolean,
- onClick: () -> Unit,
- onLongPress: () -> Unit,
- description: @Composable ColumnScope.() -> Unit
-) {
- Row(
- modifier = modifier
- .height(125.dp)
- .combinedClickable(
- onClick = onClick,
- onLongClick = onLongPress
- )
- ) {
- Box(
- Modifier
- .size(82.dp, 125.dp)
- .clip(RoundedCornerShape(8.dp))) {
- Cover(
- width = 82.dp,
- height = 125.dp,
- url = bookInformation.coverUrl,
- rounded = 8.dp
- )
- androidx.compose.animation.AnimatedVisibility(
- visible = selected,
- enter = fadeIn(),
- exit = fadeOut()
- ) {
- Box(
- Modifier
- .fillMaxSize()
- .background(
- color = MaterialTheme.colorScheme.surfaceContainerHighest.copy(
- alpha = 0.7f
- )
- )
- ) {
- val color = MaterialTheme.colorScheme.primary
- Canvas(
- Modifier
- .align(Alignment.Center)
- .size(36.dp)) {
- drawCircle(
- color = color,
- radius = 18.dp.toPx()
- )
- }
- Icon(
- modifier = Modifier
- .align(Alignment.Center)
- .size(22.dp),
- painter = painterResource(R.drawable.check_24px),
- tint = MaterialTheme.colorScheme.onPrimary,
- contentDescription = null
- )
- }
- }
- }
- Column (
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp, 2.dp, 14.dp, 5.dp)
- ) {
- Text(
- text = bookInformation.title,
- style = MaterialTheme.typography.labelLarge,
- fontWeight = FontWeight.W800,
- fontSize = 16.sp,
- lineHeight = 18.sp,
- maxLines = 2
- )
- description.invoke(this@Column)
- }
- }
-}
-
-
-
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(
scrollBehavior: TopAppBarScrollBehavior,
backgroundColor: Color,
selectMode: Boolean,
+ uiState: BookshelfHomeUiState,
onClickCreate: () -> Unit,
onClickSearch: () -> Unit,
onClickEdit: () -> Unit,
@@ -666,10 +442,12 @@ fun TopBar(
var mainMenuWidth by remember { mutableStateOf(0.dp) }
var mainMenuItemHeight by remember { mutableStateOf(0.dp) }
var exportImportMenuWidth by remember { mutableStateOf(0.dp) }
+
Box(
Modifier
.fillMaxWidth()
- .padding(horizontal = 12.dp)) {
+ .padding(horizontal = 12.dp)
+ ) {
Box(Modifier.align(Alignment.TopEnd)) {
DropdownMenu(
modifier = Modifier
@@ -681,13 +459,13 @@ fun TopBar(
},
offset = DpOffset(0.dp, (-1).dp),
expanded = mainMenuExpended,
- onDismissRequest = { mainMenuExpended = false }) {
+ onDismissRequest = { mainMenuExpended = false }
+ ) {
DropdownMenuItem(
text = {
Text(
text = "新建书架",
style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
)
},
onClick = onClickCreate
@@ -697,7 +475,6 @@ fun TopBar(
Text(
text = "编辑此书架",
style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
)
},
onClick = onClickEdit
@@ -707,7 +484,6 @@ fun TopBar(
Text(
text = "分享此书架",
style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
)
},
onClick = onClickShareBookshelf
@@ -717,7 +493,6 @@ fun TopBar(
Text(
text = "导入和导出...",
style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
)
},
trailingIcon = {
@@ -728,24 +503,9 @@ fun TopBar(
},
onClick = { exportImportMenuExpended = true }
)
- DropdownMenuItem(
- text = {
- Text(
- text = "排序方式...",
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
- )
- },
- trailingIcon = {
- Icon(
- painter = painterResource(R.drawable.arrow_right_24px),
- contentDescription = null
- )
- },
- onClick = { }
- )
}
}
+
Box(
modifier = Modifier
.align(Alignment.TopEnd)
@@ -761,15 +521,10 @@ fun TopBar(
},
offset = DpOffset(0.dp, mainMenuItemHeight.times(3.5f)),
expanded = exportImportMenuExpended,
- onDismissRequest = { exportImportMenuExpended = false }) {
+ onDismissRequest = { exportImportMenuExpended = false }
+ ) {
DropdownMenuItem(
- text = {
- Text(
- text = "导出为 .lnr",
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
- )
- },
+ text = { Text("导出为 .lnr", style = MaterialTheme.typography.bodyLarge) },
onClick = {
onClickSaveThisBookshelf()
exportImportMenuExpended = false
@@ -777,13 +532,7 @@ fun TopBar(
}
)
DropdownMenuItem(
- text = {
- Text(
- text = "导出全部为 .lnr",
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
- )
- },
+ text = { Text("导出全部为 .lnr", style = MaterialTheme.typography.bodyLarge) },
onClick = {
onClickSaveAllBookshelf()
exportImportMenuExpended = false
@@ -791,13 +540,7 @@ fun TopBar(
}
)
DropdownMenuItem(
- text = {
- Text(
- text = "从文件导入",
- style = MaterialTheme.typography.bodyLarge,
- fontWeight = FontWeight.W400
- )
- },
+ text = { Text("从文件导入", style = MaterialTheme.typography.bodyLarge) },
onClick = {
onClickImportBookshelf()
exportImportMenuExpended = false
@@ -810,8 +553,9 @@ fun TopBar(
MediumTopAppBar(
title = {
- Text(
- text = stringResource(id = R.string.nav_bookshelf),
+ AnimatedText(
+ text = if (selectMode) stringResource(R.string.nav_bookshelf_select_mode, uiState.selectedBookIds.size)
+ else stringResource(R.string.nav_bookshelf),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.W600,
color = MaterialTheme.colorScheme.onSurface,
@@ -820,7 +564,7 @@ fun TopBar(
)
},
navigationIcon = {
- AnimatedVisibility(selectMode) {
+ AnimatedVisibility(visible = selectMode) {
IconButton(onClickDisableSelectMode) {
Icon(
painter = painterResource(R.drawable.cancel_24px),
@@ -830,43 +574,53 @@ fun TopBar(
}
},
actions = {
- IconButton(
- if (!selectMode) {
- scrollBehavior.state.heightOffset = 0f
- onClickCreate
+ if (!selectMode) {
+ IconButton(onClickCreate) {
+ Icon(
+ painter = painterResource(R.drawable.library_add_24px),
+ contentDescription = "create"
+ )
+ }
+ /*IconButton(onClickSearch) {
+ Icon(
+ painter = painterResource(R.drawable.search_24px),
+ contentDescription = "search"
+ )
+ }*/
+ IconButton(onClick = { mainMenuExpended = true }) {
+ Icon(
+ painter = painterResource(R.drawable.more_vert_24px),
+ contentDescription = stringResource(R.string.more)
+ )
+ }
+ } else {
+ IconButton(onClickSelectAll) {
+ Icon(
+ painter = painterResource(R.drawable.select_all_24px),
+ contentDescription = "select all"
+ )
+ }
+ IconButton(onClickPin) {
+ Icon(
+ painter = painterResource(R.drawable.keep_24px),
+ contentDescription = "pin"
+ )
+ }
+ IconButton(onClickRemove) {
+ Icon(
+ painter = painterResource(R.drawable.bookmark_remove_24px),
+ contentDescription = "remove"
+ )
}
- else onClickSelectAll
- ) {
- Icon(
- painter = if (!selectMode) painterResource(R.drawable.library_add_24px) else painterResource(R.drawable.select_all_24px),
- contentDescription = if (!selectMode) "create" else "select all"
- )
- }
- IconButton(if (!selectMode) onClickSearch else onClickPin) {
- Icon(
- painter = if (!selectMode) painterResource(R.drawable.search_24px) else painterResource(R.drawable.keep_24px),
- contentDescription = if (!selectMode) "search" else "pin"
- )
- }
- IconButton(if (!selectMode) { { mainMenuExpended = true } } else onClickRemove) {
- Icon(
- painter = if (!selectMode) painterResource(R.drawable.more_vert_24px) else painterResource(R.drawable.bookmark_remove_24px),
- contentDescription = if (!selectMode) stringResource(R.string.more) else "remove"
- )
- }
- androidx.compose.animation.AnimatedVisibility(selectMode) {
IconButton(onClickBookmark) {
Icon(
painter = painterResource(R.drawable.outline_bookmark_24px),
- contentDescription = "mark"
+ contentDescription = "bookmark"
)
}
}
},
- windowInsets =
- WindowInsets.safeDrawing.only(
- WindowInsetsSides.Horizontal + WindowInsetsSides.Top
- ),
+ windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.mediumTopAppBarColors(
containerColor = backgroundColor,
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfUIComponents.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfUIComponents.kt
new file mode 100644
index 0000000..85c2952
--- /dev/null
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/bookshelf/home/BookshelfUIComponents.kt
@@ -0,0 +1,389 @@
+package indi.dmzz_yyhyy.lightnovelreader.ui.home.bookshelf.home
+
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Badge
+import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SwipeToDismissBox
+import androidx.compose.material3.SwipeToDismissBoxDefaults
+import androidx.compose.material3.SwipeToDismissBoxState
+import androidx.compose.material3.SwipeToDismissBoxValue
+import androidx.compose.material3.SwipeToDismissBoxValue.EndToStart
+import androidx.compose.material3.SwipeToDismissBoxValue.Settled
+import androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberSwipeToDismissBoxState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import indi.dmzz_yyhyy.lightnovelreader.R
+import indi.dmzz_yyhyy.lightnovelreader.data.book.BookInformation
+import indi.dmzz_yyhyy.lightnovelreader.ui.components.Cover
+import indi.dmzz_yyhyy.lightnovelreader.utils.SwipeAction
+
+@Composable
+fun BookCardContent(
+ selected: Boolean,
+ modifier: Modifier = Modifier,
+ bookInformation: BookInformation,
+ latestChapterTitle: String? = null
+) {
+ Row(
+ modifier = modifier.height(136.dp).padding(4.dp),
+ ) {
+ Box(
+ modifier = Modifier
+ .size(90.dp, 136.dp)
+ .clip(RoundedCornerShape(8.dp))
+ ) {
+ Box(
+ modifier = Modifier.graphicsLayer(alpha = if (selected) 0.7f else 1f)
+ ) {
+ Cover(
+ width = 90.dp,
+ height = 136.dp,
+ url = bookInformation.coverUrl,
+ rounded = 8.dp
+ )
+ if (latestChapterTitle != null) {
+ Box(
+ modifier = Modifier.padding(4.dp)
+ .align(Alignment.TopEnd)
+ ) {
+ Badge(
+ modifier = Modifier.size(12.dp),
+ )
+ }
+ }
+ }
+
+ androidx.compose.animation.AnimatedVisibility(
+ visible = selected,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ val color = MaterialTheme.colorScheme.primary
+ Canvas(
+ modifier = Modifier.size(36.dp)
+ ) {
+ drawCircle(
+ color = color,
+ radius = 18.dp.toPx(),
+ )
+ }
+ Icon(
+ modifier = Modifier
+ .size(22.dp),
+ painter = painterResource(R.drawable.check_24px),
+ tint = MaterialTheme.colorScheme.onPrimary,
+ contentDescription = null
+ )
+ }
+ }
+ }
+
+
+ Column(
+ modifier = Modifier.fillMaxWidth().fillMaxHeight()
+ .padding(start = 12.dp),
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+ val titleLineHeight = 20.sp
+ Text(
+ modifier = Modifier.height(
+ with(LocalDensity.current) { (titleLineHeight * 2).toDp() }
+ ).wrapContentHeight(Alignment.CenterVertically),
+ text = bookInformation.title,
+ maxLines = 2,
+ fontWeight = FontWeight.Bold,
+ fontSize = 16.sp,
+ lineHeight = titleLineHeight,
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = bookInformation.author,
+ maxLines = 1,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.primary,
+ lineHeight = 20.sp,
+ fontSize = 14.sp,
+ )
+ BookStatusIcon(bookInformation)
+ }
+ Row (
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ fontSize = 14.sp,
+ lineHeight = 14.sp,
+ text = stringResource(
+ R.string.book_info_update_date,
+ bookInformation.lastUpdated.year,
+ bookInformation.lastUpdated.monthValue,
+ bookInformation.lastUpdated.dayOfMonth
+ )
+ )
+ Text(
+ fontSize = 14.sp,
+ lineHeight = 14.sp,
+ text = stringResource(
+ R.string.book_info_word_count_kilo,
+ bookInformation.wordCount / 1000
+ )
+ )
+ }
+ if (latestChapterTitle == null) {
+ Text(
+ text = bookInformation.description.trim(),
+ maxLines = 2,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
+ lineHeight = 18.sp,
+ )
+ } else {
+ Column {
+ Row {
+
+ Text(
+ text = "已更新至: ",
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
+ )
+ }
+ Row {
+ Text(
+ text = latestChapterTitle,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.primary,
+ fontSize = 14.sp,
+ lineHeight = 18.sp,
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun BookStatusIcon(bookInformation: BookInformation) {
+ val modifier = Modifier.height(16.dp).width(16.dp)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (bookInformation.isComplete) {
+ Icon(
+ modifier = modifier,
+ painter = painterResource(R.drawable.done_all_24px),
+ contentDescription = "Completed",
+ tint = MaterialTheme.colorScheme.outline
+ )
+ } else {
+ Icon(
+ modifier = modifier,
+ painter = painterResource(R.drawable.hourglass_top_24px),
+ contentDescription = "In Progress",
+ tint = MaterialTheme.colorScheme.outline
+ )
+ }
+
+ // 可实现: 已动画化标识
+
+ /*if (bookInformation.isAnimated) {
+ Icon(
+ modifier = modifier,
+ painter = painterResource(R.drawable.live_tv_24px),
+ contentDescription = "Animated",
+ tint = MaterialTheme.colorScheme.outline
+ )
+ }*/
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
+@Composable
+fun BookCardItem(
+ modifier: Modifier = Modifier,
+ bookInformation: BookInformation,
+ selected: Boolean = false,
+ onClick: () -> Unit,
+ onLongPress: () -> Unit,
+ latestChapterTitle: String? = null,
+ swipeToRightAction: SwipeAction = SwipeAction.None,
+ swipeToLeftAction: SwipeAction = SwipeAction.None,
+ progress: (SwipeAction) -> Unit?,
+ haptic: HapticFeedback
+){
+
+ val dismissState = rememberNoFlingSwipeToDismissBoxState(
+ positionalThreshold = { it * 0.6f },
+ confirmValueChange = {
+ when (it) {
+ StartToEnd -> {
+ haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+ progress.invoke(swipeToRightAction)
+ }
+ EndToStart -> {
+ haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+ progress.invoke(swipeToLeftAction)
+ }
+ Settled -> { }
+ }
+ false
+ },
+ )
+
+ LaunchedEffect(dismissState.dismissDirection) {
+ if (dismissState.dismissDirection != Settled) {
+ haptic.performHapticFeedback(HapticFeedbackType.LongPress)
+ }
+ }
+
+ val backgroundColor by animateColorAsState(
+ targetValue = if (selected) MaterialTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.surface,
+ animationSpec = tween(durationMillis = 300)
+ )
+
+ Card {
+ SwipeToDismissBox(
+ state = dismissState,
+ modifier = modifier,
+ enableDismissFromEndToStart = swipeToLeftAction != SwipeAction.None,
+ enableDismissFromStartToEnd = swipeToRightAction != SwipeAction.None,
+ backgroundContent = {
+ DismissBackground(
+ dismissState = dismissState,
+ swipeToLeftAction = swipeToLeftAction,
+ swipeToRightAction = swipeToRightAction
+ )
+ },
+ content = {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(12.dp))
+ .background(backgroundColor)
+ .combinedClickable(
+ onClick = onClick,
+ onLongClick = onLongPress,
+ )
+ ) {
+ BookCardContent(
+ selected = selected,
+ latestChapterTitle = latestChapterTitle,
+ bookInformation = bookInformation
+ )
+ }
+ }
+ )
+ }
+}
+
+@Composable
+@ExperimentalMaterial3Api
+fun rememberNoFlingSwipeToDismissBoxState(
+ initialValue: SwipeToDismissBoxValue = Settled,
+ confirmValueChange: (SwipeToDismissBoxValue) -> Boolean = { true },
+ positionalThreshold: (totalDistance: Float) -> Float =
+ SwipeToDismissBoxDefaults.positionalThreshold,
+): SwipeToDismissBoxState {
+ val density = Density(Float.POSITIVE_INFINITY)
+ return rememberSaveable(
+ saver = SwipeToDismissBoxState.Saver(
+ confirmValueChange = confirmValueChange,
+ density = density,
+ positionalThreshold = positionalThreshold
+ )
+ ) {
+ SwipeToDismissBoxState(initialValue, density, confirmValueChange, positionalThreshold)
+ }
+}
+
+@Composable
+private fun DismissBackground(
+ dismissState: SwipeToDismissBoxState,
+ swipeToRightAction: SwipeAction,
+ swipeToLeftAction: SwipeAction
+) {
+ val color = when (dismissState.dismissDirection) {
+ StartToEnd -> swipeToRightAction.color
+ EndToStart -> swipeToLeftAction.color
+ Settled -> Color.Transparent
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color)
+ .clip(RoundedCornerShape(12.dp))
+ .padding(28.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ if (dismissState.dismissDirection == StartToEnd) {
+ Icon(
+ painter = painterResource(id = swipeToRightAction.iconRes),
+ contentDescription = swipeToRightAction.description,
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ if (dismissState.dismissDirection == EndToStart) {
+ Icon(
+ painter = painterResource(id = swipeToLeftAction.iconRes),
+ contentDescription = swipeToLeftAction.description,
+ tint = MaterialTheme.colorScheme.surface
+ )
+ }
+ }
+}
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/settings/SettingsScreen.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/settings/SettingsScreen.kt
index 7cab9db..3e6df71 100644
--- a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/settings/SettingsScreen.kt
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/ui/home/settings/SettingsScreen.kt
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
@@ -84,7 +83,7 @@ fun SettingsScreen(
Modifier.verticalScroll(rememberScrollState())
.nestedScroll(pinnedScrollBehavior.nestedScrollConnection)
) {
- SettingsCard(
+ SettingsCategory(
title = stringResource(R.string.app_settings),
icon = ImageVector.vectorResource(R.drawable.outline_settings_24px)
) {
@@ -93,20 +92,20 @@ fun SettingsScreen(
checkUpdate = checkUpdate
)
}
- SettingsCard(
+ SettingsCategory(
title = stringResource(R.string.display_settings),
icon = ImageVector.vectorResource(R.drawable.light_mode_24px)
) {
DisplaySettingsList(settingState = settingState)
}
- /*SettingsCard(
+ /*SettingsCategory(
title = "阅读",
icon = ImageVector.vectorResource(R.drawable.outline_bookmark_24px),
content = { ReaderSettingsList(
state = state,
) }
)*/
- SettingsCard(
+ SettingsCategory(
title = "数据",
icon = ImageVector.vectorResource(R.drawable.hard_disk_24px)
) {
@@ -120,7 +119,7 @@ fun SettingsScreen(
webDataSourceId = viewModel.webBookDataSourceId,
)
}
- SettingsCard(
+ SettingsCategory(
title = stringResource(R.string.about_settings),
icon = ImageVector.vectorResource(R.drawable.info_24px)
) {
@@ -160,62 +159,53 @@ private fun TopBar(
)
}
-
@Composable
-fun SettingsCard(
+fun SettingsCategory(
title: String,
icon: ImageVector,
content: @Composable ColumnScope.() -> Unit
) {
var expanded by remember { mutableStateOf(true) }
-
Card(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp, bottom = 8.dp, start = 14.dp, end = 14.dp),
+ modifier = Modifier.fillMaxWidth()
+ .padding(vertical = 8.dp, horizontal = 14.dp),
colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
+ containerColor = MaterialTheme.colorScheme.surfaceContainerLow
),
shape = RoundedCornerShape(16.dp),
- elevation = CardDefaults.elevatedCardElevation(2.dp)
) {
Column {
Row(
- modifier = Modifier
- .fillMaxWidth()
- .height(68.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
) {
- Row(
+ Box(
modifier = Modifier
- .weight(1f)
- .padding(start = 18.dp),
- verticalAlignment = Alignment.CenterVertically
+ .padding(start = 14.dp)
+ .size(40.dp)
+ .background(
+ shape = CircleShape,
+ color = MaterialTheme.colorScheme.surfaceContainerHigh
+ ),
+ contentAlignment = Alignment.Center
) {
- Box(
- modifier = Modifier
- .size(40.dp)
- .background(
- color = MaterialTheme.colorScheme.surfaceContainerHighest,
- shape = CircleShape
- ),
- contentAlignment = Alignment.Center
- ) {
- Icon(
- imageVector = icon,
- contentDescription = null,
- tint = MaterialTheme.colorScheme.primary
- )
- }
- Text(
- text = title,
- style = MaterialTheme.typography.titleLarge,
- fontWeight = FontWeight.Bold,
- fontSize = 18.sp,
- modifier = Modifier.padding(start = 16.dp)
+ Icon(
+ imageVector = icon,
+ tint = MaterialTheme.colorScheme.primary,
+ contentDescription = null,
)
}
+
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ fontSize = 18.sp,
+ modifier = Modifier
+ .weight(1f)
+ .padding(horizontal = 14.dp)
+ )
+
IconButton(
onClick = { expanded = !expanded },
modifier = Modifier.padding(16.dp)
@@ -232,7 +222,7 @@ fun SettingsCard(
) {
Column(
modifier = Modifier.clip(RoundedCornerShape(16.dp)),
- verticalArrangement = Arrangement.spacedBy(8.dp),
+ verticalArrangement = Arrangement.spacedBy(6.dp),
content = content
)
}
diff --git a/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/utils/SwipeAction.kt b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/utils/SwipeAction.kt
new file mode 100644
index 0000000..603f8d2
--- /dev/null
+++ b/app/src/main/kotlin/indi/dmzz_yyhyy/lightnovelreader/utils/SwipeAction.kt
@@ -0,0 +1,64 @@
+package indi.dmzz_yyhyy.lightnovelreader.utils
+
+import androidx.compose.ui.graphics.Color
+import indi.dmzz_yyhyy.lightnovelreader.R
+
+@Suppress("LeakingThis")
+sealed class SwipeAction(
+ val id: String,
+ val iconRes: Int,
+ val color: Color,
+ val description: String,
+) {
+
+ companion object {
+ private val _all = mutableMapOf()
+ val all: Map = _all
+ }
+
+ init {
+ _all[id] = this
+ }
+
+ data object AddToBookshelf : SwipeAction(
+ id = "add_to_bookshelf",
+ iconRes = R.drawable.bookmark_add_24px,
+ color = Color(0xff2ECC71),
+ description = ""
+ )
+
+ data object RemoveFromBookshelf : SwipeAction(
+ id = "remove_from_bookshelf",
+ iconRes = R.drawable.delete_forever_24px,
+ color = Color(0xffE74C3C),
+ description = ""
+ )
+
+ data object Pin : SwipeAction(
+ id = "pin",
+ iconRes = R.drawable.keep_24px,
+ color = Color(0xff007AFF),
+ description = ""
+ )
+
+ data object Expand : SwipeAction(
+ id = "expand",
+ iconRes = R.drawable.expand_circle_down_24px,
+ color = Color(0xffF1C40F),
+ description = ""
+ )
+
+ data object Info : SwipeAction(
+ id = "info",
+ iconRes = R.drawable.info_24px,
+ color = Color(0xffE67E22),
+ description = ""
+ )
+
+ data object None : SwipeAction(
+ id = "none",
+ iconRes = R.drawable.block_24px,
+ color = Color.Transparent,
+ description = ""
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/blank_24px.xml b/app/src/main/res/drawable/blank_24px.xml
new file mode 100644
index 0000000..a806558
--- /dev/null
+++ b/app/src/main/res/drawable/blank_24px.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/app/src/main/res/drawable/block_24px.xml b/app/src/main/res/drawable/block_24px.xml
new file mode 100644
index 0000000..540750b
--- /dev/null
+++ b/app/src/main/res/drawable/block_24px.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/expand_circle_down_24px.xml b/app/src/main/res/drawable/expand_circle_down_24px.xml
new file mode 100644
index 0000000..74c73f6
--- /dev/null
+++ b/app/src/main/res/drawable/expand_circle_down_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/hourglass_top_24px.xml b/app/src/main/res/drawable/hourglass_top_24px.xml
new file mode 100644
index 0000000..1d119b5
--- /dev/null
+++ b/app/src/main/res/drawable/hourglass_top_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/live_tv_24px.xml b/app/src/main/res/drawable/live_tv_24px.xml
new file mode 100644
index 0000000..90ab05d
--- /dev/null
+++ b/app/src/main/res/drawable/live_tv_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 9b79e70..010657a 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -3,6 +3,7 @@
阅读中
书架
+ 选择 书架 (%d)
探索
首页
分类
@@ -76,5 +77,8 @@
文库
文库筛选
根据小说文库的筛选。
+ 更新: %s-%s-%s
+ %dK 字
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 01f9edb..6ec23af 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4,6 +4,7 @@
In Reading
Bookshelf
+ Select Bookshelf (%d)
Exploration
Homepage
Categories
@@ -78,5 +79,8 @@
Publishing House
Filter by publishing house
Filter by the novel\'s publishing house.
+ Updated: %s-%s-%s
+ %dK words
+
\ No newline at end of file