[优化] 实现了更多原书架功能(日志、快速滚动条等),修复了一些问题
This commit is contained in:
12 files changed
+299
-57
No files matched your search
@@ -0,0 +1,124 @@
|
|||||||
|
package io.legado.app.ui.about
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DeleteSweep
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.legado.app.R
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||||
|
import io.legado.app.utils.LogUtils
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppLogSheet(
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
var logs by remember { mutableStateOf(AppLog.logs) }
|
||||||
|
var showStackTrace by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
GlassModalBottomSheet(
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.log),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
IconButton(onClick = {
|
||||||
|
AppLog.clear()
|
||||||
|
logs = emptyList()
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.DeleteSweep, contentDescription = "Clear")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentPadding = PaddingValues(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
items(logs) { item ->
|
||||||
|
LogItem(item) {
|
||||||
|
item.third?.let {
|
||||||
|
showStackTrace = it.stackTraceToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showStackTrace?.let { stackTrace ->
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showStackTrace = null },
|
||||||
|
title = { Text("Log") },
|
||||||
|
text = {
|
||||||
|
SelectionContainer {
|
||||||
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
|
Text(text = stackTrace, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { showStackTrace = null }) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LogItem(
|
||||||
|
item: Triple<Long, String, Throwable?>,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = LogUtils.logTimeFormat.format(Date(item.first)),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = item.second,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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.layout.size
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -22,7 +22,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedIconButton
|
import androidx.compose.material3.OutlinedIconButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -41,8 +40,8 @@ import io.legado.app.data.entities.BookGroup
|
|||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
import io.legado.app.ui.widget.components.cover.Cover
|
import io.legado.app.ui.widget.components.cover.Cover
|
||||||
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
|
||||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
import io.legado.app.ui.widget.components.settingItem.CompactDropdownSettingItem
|
||||||
import io.legado.app.ui.widget.components.settingItem.SettingItem
|
import io.legado.app.ui.widget.components.settingItem.CompactSwitchSettingItem
|
||||||
import io.legado.app.utils.FileUtils
|
import io.legado.app.utils.FileUtils
|
||||||
import io.legado.app.utils.MD5Utils
|
import io.legado.app.utils.MD5Utils
|
||||||
import io.legado.app.utils.SelectImageContract
|
import io.legado.app.utils.SelectImageContract
|
||||||
@@ -128,7 +127,7 @@ fun GroupEditContent(
|
|||||||
Cover(
|
Cover(
|
||||||
path = coverPath,
|
path = coverPath,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 90.dp, height = 130.dp)
|
.width(96.dp)
|
||||||
.clickable { selectImage.launch() }
|
.clickable { selectImage.launch() }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,7 +143,7 @@ fun GroupEditContent(
|
|||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownListSettingItem(
|
CompactDropdownSettingItem(
|
||||||
title = stringResource(R.string.sort),
|
title = stringResource(R.string.sort),
|
||||||
selectedValue = selectedSortIndex.toString(),
|
selectedValue = selectedSortIndex.toString(),
|
||||||
displayEntries = sortOptions,
|
displayEntries = sortOptions,
|
||||||
@@ -158,15 +157,10 @@ fun GroupEditContent(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingItem(
|
CompactSwitchSettingItem(
|
||||||
title = stringResource(R.string.allow_drop_down_refresh),
|
title = stringResource(R.string.allow_drop_down_refresh),
|
||||||
trailingContent = {
|
checked = enableRefresh,
|
||||||
Switch(
|
onCheckedChange = { enableRefresh = it }
|
||||||
checked = enableRefresh,
|
|
||||||
onCheckedChange = { enableRefresh = it }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = { enableRefresh = !enableRefresh }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|||||||
@@ -74,18 +74,18 @@ fun BookshelfItem(
|
|||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
val coverModifier = if (coverShadow) {
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.shadow(4.dp, MaterialTheme.shapes.extraSmall)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.clip(MaterialTheme.shapes.extraSmall)
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (coverShadow) Modifier.shadow(
|
||||||
|
4.dp,
|
||||||
|
MaterialTheme.shapes.extraSmall
|
||||||
|
) else Modifier
|
||||||
|
)
|
||||||
|
.clip(MaterialTheme.shapes.extraSmall) // 先阴影后裁剪
|
||||||
) {
|
) {
|
||||||
cover(coverModifier)
|
cover(Modifier.fillMaxWidth())
|
||||||
if (gridStyle == 1) {
|
if (gridStyle == 1) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
@@ -96,6 +96,7 @@ fun BookshelfItem(
|
|||||||
blurRadius = 4f
|
blurRadius = 4f
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import androidx.activity.compose.BackHandler
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
@@ -35,8 +35,8 @@ import androidx.compose.material3.PrimaryScrollableTabRow
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -57,6 +57,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
|
import io.legado.app.ui.about.AppLogSheet
|
||||||
import io.legado.app.ui.book.cache.CacheActivity
|
import io.legado.app.ui.book.cache.CacheActivity
|
||||||
import io.legado.app.ui.book.import.local.ImportBookActivity
|
import io.legado.app.ui.book.import.local.ImportBookActivity
|
||||||
import io.legado.app.ui.book.import.remote.RemoteBookActivity
|
import io.legado.app.ui.book.import.remote.RemoteBookActivity
|
||||||
@@ -67,6 +68,7 @@ import io.legado.app.ui.file.HandleFileContract
|
|||||||
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||||
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
|
||||||
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
|
||||||
|
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
|
||||||
import io.legado.app.ui.widget.components.list.ListScaffold
|
import io.legado.app.ui.widget.components.list.ListScaffold
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||||
import io.legado.app.utils.readText
|
import io.legado.app.utils.readText
|
||||||
@@ -94,6 +96,7 @@ fun BookshelfScreen(
|
|||||||
var showImportSheet by remember { mutableStateOf(false) }
|
var showImportSheet by remember { mutableStateOf(false) }
|
||||||
var showConfigSheet by remember { mutableStateOf(false) }
|
var showConfigSheet by remember { mutableStateOf(false) }
|
||||||
var showGroupManageSheet by remember { mutableStateOf(false) }
|
var showGroupManageSheet by remember { mutableStateOf(false) }
|
||||||
|
var showLogSheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val importLauncher = rememberLauncherForActivityResult(HandleFileContract()) {
|
val importLauncher = rememberLauncherForActivityResult(HandleFileContract()) {
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -232,6 +235,7 @@ fun BookshelfScreen(
|
|||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.log)) },
|
text = { Text(stringResource(R.string.log)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
showLogSheet = true
|
||||||
dismiss()
|
dismiss()
|
||||||
},
|
},
|
||||||
leadingIcon = { Icon(Icons.Default.History, null) }
|
leadingIcon = { Icon(Icons.Default.History, null) }
|
||||||
@@ -240,8 +244,11 @@ fun BookshelfScreen(
|
|||||||
bottomContent = if (bookGroupStyle == 0) {
|
bottomContent = if (bookGroupStyle == 0) {
|
||||||
{
|
{
|
||||||
if (uiState.groups.isNotEmpty()) {
|
if (uiState.groups.isNotEmpty()) {
|
||||||
|
val selectedTabIndex = remember(pagerState.currentPage, uiState.groups.size) {
|
||||||
|
pagerState.currentPage.coerceIn(0, uiState.groups.size - 1)
|
||||||
|
}
|
||||||
PrimaryScrollableTabRow(
|
PrimaryScrollableTabRow(
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = selectedTabIndex,
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
divider = { },
|
divider = { },
|
||||||
containerColor = GlassTopAppBarDefaults.containerColor(),
|
containerColor = GlassTopAppBarDefaults.containerColor(),
|
||||||
@@ -249,7 +256,7 @@ fun BookshelfScreen(
|
|||||||
) {
|
) {
|
||||||
uiState.groups.forEachIndexed { index, group ->
|
uiState.groups.forEachIndexed { index, group ->
|
||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == index,
|
selected = selectedTabIndex == index,
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch { pagerState.animateScrollToPage(index) }
|
scope.launch { pagerState.animateScrollToPage(index) }
|
||||||
},
|
},
|
||||||
@@ -271,31 +278,28 @@ fun BookshelfScreen(
|
|||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
val pullToRefreshState = rememberPullToRefreshState()
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
val currentGroup = uiState.groups.getOrNull(pagerState.currentPage)
|
||||||
|
val pullToRefreshEnabled = currentGroup?.enableRefresh ?: true
|
||||||
|
|
||||||
PullToRefreshBox(
|
Box(
|
||||||
isRefreshing = isRefreshing,
|
modifier = Modifier
|
||||||
onRefresh = {
|
.fillMaxSize()
|
||||||
scope.launch {
|
.pullToRefresh(
|
||||||
isRefreshing = true
|
|
||||||
viewModel.upToc(uiState.items)
|
|
||||||
delay(1000)
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state = pullToRefreshState,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
indicator = {
|
|
||||||
PullToRefreshDefaults.LoadingIndicator(
|
|
||||||
state = pullToRefreshState,
|
state = pullToRefreshState,
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
modifier = Modifier
|
onRefresh = {
|
||||||
.align(Alignment.TopCenter)
|
scope.launch {
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
isRefreshing = true
|
||||||
|
viewModel.upToc(uiState.items)
|
||||||
|
delay(1000)
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = pullToRefreshEnabled
|
||||||
)
|
)
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
if (bookGroupStyle == 2 && isInFolderRoot) {
|
if (bookGroupStyle == 2 && isInFolderRoot) {
|
||||||
LazyVerticalGrid(
|
FastScrollLazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
@@ -305,7 +309,8 @@ fun BookshelfScreen(
|
|||||||
end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
|
end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp)
|
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
||||||
|
showFastScroll = BookshelfConfig.showBookshelfFastScroller
|
||||||
) {
|
) {
|
||||||
items(uiState.groups) { group ->
|
items(uiState.groups) { group ->
|
||||||
if (bookshelfLayoutMode == 0) {
|
if (bookshelfLayoutMode == 0) {
|
||||||
@@ -369,6 +374,14 @@ fun BookshelfScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullToRefreshDefaults.LoadingIndicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = isRefreshing,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +422,10 @@ fun BookshelfScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showLogSheet) {
|
||||||
|
AppLogSheet(onDismissRequest = { showLogSheet = false })
|
||||||
|
}
|
||||||
|
|
||||||
if (uiState.isLoading) {
|
if (uiState.isLoading) {
|
||||||
Dialog(onDismissRequest = {}) {
|
Dialog(onDismissRequest = {}) {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -443,7 +460,7 @@ fun BookshelfPage(
|
|||||||
onBookClick: (Book) -> Unit,
|
onBookClick: (Book) -> Unit,
|
||||||
onBookLongClick: (Book) -> Unit
|
onBookLongClick: (Book) -> Unit
|
||||||
) {
|
) {
|
||||||
LazyVerticalGrid(
|
FastScrollLazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
@@ -453,7 +470,8 @@ fun BookshelfPage(
|
|||||||
end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
|
end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp)
|
horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
|
||||||
|
showFastScroll = BookshelfConfig.showBookshelfFastScroller
|
||||||
) {
|
) {
|
||||||
items(books, key = { it.bookUrl }) { book ->
|
items(books, key = { it.bookUrl }) { book ->
|
||||||
BookItem(
|
BookItem(
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ fun GroupManageSheet(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 32.dp)
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package io.legado.app.ui.widget.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchColors
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IconSwitch(
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
checkedIcon: ImageVector = Icons.Filled.Check,
|
||||||
|
uncheckedIcon: ImageVector? = null,
|
||||||
|
showIcon: Boolean = true,
|
||||||
|
colors: SwitchColors = SwitchDefaults.colors()
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = colors,
|
||||||
|
thumbContent = {
|
||||||
|
if (!showIcon) return@Switch
|
||||||
|
|
||||||
|
val icon = if (checked) checkedIcon else uncheckedIcon
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(SwitchDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -37,7 +37,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.graphics.withSave
|
import androidx.core.graphics.withSave
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.model.BookCover
|
import io.legado.app.model.BookCover
|
||||||
import io.legado.app.ui.config.coverConfig.CoverConfig
|
import io.legado.app.ui.config.coverConfig.CoverConfig
|
||||||
import io.legado.app.ui.widget.components.card.TextCard
|
import io.legado.app.ui.widget.components.card.TextCard
|
||||||
@@ -57,7 +56,7 @@ fun BookCover(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isNight = isSystemInDarkTheme()
|
val isNight = isSystemInDarkTheme()
|
||||||
|
|
||||||
val useDefault = AppConfig.useDefaultCover
|
val useDefault = CoverConfig.useDefaultCover
|
||||||
val finalPath = if (useDefault) null else path
|
val finalPath = if (useDefault) null else path
|
||||||
|
|
||||||
// 为每个 BookCover 组件实例基于其书名或路径生成一个随机封面
|
// 为每个 BookCover 组件实例基于其书名或路径生成一个随机封面
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fun Cover(
|
|||||||
val boxModifier = modifier
|
val boxModifier = modifier
|
||||||
.aspectRatio(3f / 4f)
|
.aspectRatio(3f / 4f)
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
|
||||||
Box(modifier = boxModifier) {
|
Box(modifier = boxModifier) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import androidx.compose.foundation.layout.calculateEndPadding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -80,3 +85,52 @@ fun FastScrollLazyColumn(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FastScrollLazyVerticalGrid(
|
||||||
|
columns: GridCells,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: LazyGridState = rememberLazyGridState(),
|
||||||
|
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||||
|
reverseLayout: Boolean = false,
|
||||||
|
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||||
|
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
|
||||||
|
userScrollEnabled: Boolean = true,
|
||||||
|
showFastScroll: Boolean = true,
|
||||||
|
content: LazyGridScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
if (showFastScroll) {
|
||||||
|
VerticalGridFastScroller(
|
||||||
|
state = state,
|
||||||
|
columns = columns,
|
||||||
|
arrangement = horizontalArrangement,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
modifier = modifier,
|
||||||
|
topContentPadding = contentPadding.calculateTopPadding(),
|
||||||
|
endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
|
) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = columns,
|
||||||
|
state = state,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
reverseLayout = reverseLayout,
|
||||||
|
verticalArrangement = verticalArrangement,
|
||||||
|
horizontalArrangement = horizontalArrangement,
|
||||||
|
userScrollEnabled = userScrollEnabled,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = columns,
|
||||||
|
modifier = modifier,
|
||||||
|
state = state,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
reverseLayout = reverseLayout,
|
||||||
|
verticalArrangement = verticalArrangement,
|
||||||
|
horizontalArrangement = horizontalArrangement,
|
||||||
|
userScrollEnabled = userScrollEnabled,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TooltipAnchorPosition
|
import androidx.compose.material3.TooltipAnchorPosition
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.animateFloatingActionButton
|
import androidx.compose.material3.animateFloatingActionButton
|
||||||
import androidx.compose.material3.rememberTooltipState
|
import androidx.compose.material3.rememberTooltipState
|
||||||
@@ -40,6 +39,7 @@ import androidx.compose.ui.zIndex
|
|||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import io.legado.app.ui.theme.responsiveHazeEffect
|
import io.legado.app.ui.theme.responsiveHazeEffect
|
||||||
import io.legado.app.ui.theme.responsiveHazeSource
|
import io.legado.app.ui.theme.responsiveHazeSource
|
||||||
|
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
|
||||||
import io.legado.app.ui.widget.components.SelectionActions
|
import io.legado.app.ui.widget.components.SelectionActions
|
||||||
import io.legado.app.ui.widget.components.SelectionBottomBar
|
import io.legado.app.ui.widget.components.SelectionBottomBar
|
||||||
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
|
||||||
@@ -83,7 +83,7 @@ fun <T> ListScaffold(
|
|||||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||||
content: @Composable (PaddingValues) -> Unit
|
content: @Composable (PaddingValues) -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
val hazeState = remember { HazeState() }
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
+10
@@ -1,8 +1,10 @@
|
|||||||
package io.legado.app.ui.widget.components.modalBottomSheet
|
package io.legado.app.ui.widget.components.modalBottomSheet
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
@@ -16,6 +18,8 @@ import androidx.compose.material3.rememberModalBottomSheetState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
import io.legado.app.ui.theme.rememberOpaqueColorScheme
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@@ -30,6 +34,10 @@ fun GlassModalBottomSheet(
|
|||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = rememberOpaqueColorScheme()
|
val colorScheme = rememberOpaqueColorScheme()
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val maxHeight = with(density) {
|
||||||
|
LocalWindowInfo.current.containerSize.height.toDp() * 0.8f
|
||||||
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@@ -46,6 +54,8 @@ fun GlassModalBottomSheet(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = maxHeight)
|
||||||
|
.animateContentSize()
|
||||||
.then(modifier),
|
.then(modifier),
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package io.legado.app.ui.widget.components.settingItem
|
package io.legado.app.ui.widget.components.settingItem
|
||||||
|
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import io.legado.app.ui.widget.components.IconSwitch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchSettingItem(
|
fun SwitchSettingItem(
|
||||||
@@ -22,7 +22,7 @@ fun SwitchSettingItem(
|
|||||||
color = color,
|
color = color,
|
||||||
onClick = { if (enabled) onCheckedChange(!checked) },
|
onClick = { if (enabled) onCheckedChange(!checked) },
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
IconSwitch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = onCheckedChange,
|
onCheckedChange = onCheckedChange,
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
|
|||||||
Reference in new issue
Block a user