代码优化

This commit is contained in:
HapeLee
2026-05-13 21:22:44 +08:00
parent 4beeb2d4f6
commit 876f1370ec
21 changed files with 126200 additions and 331 deletions
@@ -38,6 +38,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import splitties.init.appCtx
import splitties.systemservices.notificationManager
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import kotlin.math.min
@@ -69,7 +70,7 @@ class CacheBookService : BaseService() {
private val downloadJobLock = Any()
private var downloadJob: Job? = null
private var notificationContent = appCtx.getString(R.string.service_starting)
private var mutex = Mutex()
private val bookLocks = ConcurrentHashMap<String, Mutex>()
private var lastDiagnosticsLogTime = 0L
private val notificationBuilder by lazy {
val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload)
@@ -297,7 +298,8 @@ class CacheBookService : BaseService() {
if (chapterCount == 0) {
cacheBook.setLoading()
mutex.withLock {
val bookMutex = bookLocks.getOrPut(request.bookUrl) { Mutex() }
bookMutex.withLock {
val name = book.name
if (!admission.isCurrent()) {
CacheBook.removeModelFromService(request.bookUrl, cacheBook)
@@ -371,8 +373,8 @@ class CacheBookService : BaseService() {
}
private fun admittedBookUrls(): Set<String> {
return CacheBook.cacheBookMap.keys.toHashSet().apply {
synchronized(admissionLock) {
return synchronized(admissionLock) {
CacheBook.cacheBookMap.keys.toHashSet().apply {
addAll(admittingBookUrls)
}
}
@@ -431,21 +433,24 @@ class CacheBookService : BaseService() {
}
private fun finishAdmissionJob(bookUrl: String) {
var restart = false
val waiters = synchronized(admissionLock) {
val buffer = admissionBuffers[bookUrl]
if (buffer != null && buffer.isNotEmpty()) {
restart = true
emptyList()
} else {
admissionBuffers.remove(bookUrl)
admittingBookUrls.remove(bookUrl)
admissionIdleWaiters.remove(bookUrl).orEmpty()
// New requests arrived while job was finishing — restart immediately.
startAdmissionJob(bookUrl)
return
}
}
if (restart) {
startAdmissionJob(bookUrl)
return
admissionBuffers.remove(bookUrl)
admittingBookUrls.remove(bookUrl)
// Re-check: a concurrent submitDownloadRequest may have added a request
// between the buffer empty-check and the admittingBookUrls removal.
val recheck = admissionBuffers[bookUrl]
if (recheck != null && recheck.isNotEmpty()) {
admittingBookUrls.add(bookUrl)
startAdmissionJob(bookUrl)
return
}
admissionIdleWaiters.remove(bookUrl).orEmpty()
}
waiters.forEach { it.complete(Unit) }
}
@@ -480,26 +485,23 @@ class CacheBookService : BaseService() {
private suspend fun runDownloadLoop() {
try {
var lastActiveTime = System.currentTimeMillis()
var idleSince = -1L
while (currentCoroutineContext().isActive) {
drainPendingDownloadRequests()
if (CacheBook.isGloballyPaused) {
break
}
val isActiveNow = CacheBook.isRun
|| hasPendingDownloadRequests()
|| hasAdmittingRequests()
if (isActiveNow) {
lastActiveTime = System.currentTimeMillis()
} else {
break
}
if (!CacheBook.isRun) {
if (System.currentTimeMillis() - lastActiveTime > MAX_IDLE_SPIN_MS) break
delay(200)
if (CacheBook.isRun) {
idleSince = -1L
CacheBook.startProcessJob(cachePool)
continue
}
CacheBook.startProcessJob(cachePool)
// !isRun — only keep looping while there is pending/admitting work.
if (!hasPendingDownloadRequests() && !hasAdmittingRequests()) break
val now = System.currentTimeMillis()
if (idleSince < 0) idleSince = now
if (now - idleSince > MAX_IDLE_SPIN_MS) break
delay(200)
}
} finally {
val finishedJob = currentCoroutineContext()[Job]
@@ -136,7 +136,9 @@ fun BookInfoScreen(
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class,
ExperimentalMaterial3ExpressiveApi::class
)
@Composable
private fun BookInfoScreenContent(
state: BookInfoUiState,
@@ -2,6 +2,7 @@ package io.legado.app.ui.config.themeManage
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -29,6 +30,7 @@ import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.help.config.ThemeExportData
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
@@ -74,7 +76,8 @@ fun EditThemeSheet(
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.navigationBarsPadding()
.padding(bottom = 32.dp)
.padding(bottom = 32.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Name
AppTextField(
@@ -84,7 +87,6 @@ fun EditThemeSheet(
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(8.dp))
@@ -5,18 +5,21 @@ import android.os.Looper
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.graphics.Brush
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
@@ -119,14 +122,17 @@ fun ThemeManageScreen(
)
}
) { paddingValues ->
LazyColumn(
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize(),
contentPadding = adaptiveContentPadding(
top = paddingValues.calculateTopPadding(),
bottom = 120.dp
)
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item {
item(span = { GridItemSpan(maxLineSpan) }) {
SplicedColumnGroup {
ClickableSettingItem(
title = "保存当前设置",
@@ -155,12 +161,12 @@ fun ThemeManageScreen(
}
if (savedThemes.isNotEmpty()) {
item {
item(span = { GridItemSpan(maxLineSpan) }) {
AppText(
text = "已保存的主题",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp)
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
)
}
@@ -288,79 +294,112 @@ private fun SavedThemeItem(
) {
GlassCard(
onClick = onApply,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
cornerRadius = 20.dp
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 8.dp, top = 12.dp, bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically
Column(
modifier = Modifier.fillMaxWidth()
) {
val previewColor = if (theme.data.themeColor != 0) {
Color(theme.data.themeColor)
} else if (theme.data.cPrimary != 0) {
Color(theme.data.cPrimary)
} else {
MaterialTheme.colorScheme.primary
val lightPrimary = if (theme.data.themeColor != 0) Color(theme.data.themeColor)
else if (theme.data.cPrimary != 0) Color(theme.data.cPrimary)
else MaterialTheme.colorScheme.primary
val darkPrimary = if (theme.data.cNPrimary != 0) Color(theme.data.cNPrimary)
else lightPrimary
val lightBg = if (theme.data.themeBackgroundColor != 0) Color(theme.data.themeBackgroundColor)
else Color(0xFFF7F2FA)
val darkBg = if (theme.data.isPureBlack) Color.Black else Color(0xFF1C1B1F)
// 预览区域
Column(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1.2f)
) {
// 日间行
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.background(
Brush.horizontalGradient(
0.5f to lightBg,
0.8f to lightPrimary
)
)
) {
AppText(
text = "日间",
style = MaterialTheme.typography.labelMediumEmphasized,
color = if (theme.data.primaryTextColor != 0) Color(theme.data.primaryTextColor).copy(alpha = 0.6f)
else Color.Black.copy(alpha = 0.5f),
modifier = Modifier
.align(Alignment.TopStart)
.padding(12.dp)
)
}
// 夜间行
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.background(
Brush.horizontalGradient(
0.5f to darkBg,
0.8f to darkPrimary
)
)
) {
AppText(
text = "夜间",
style = MaterialTheme.typography.labelMediumEmphasized,
color = Color.White.copy(alpha = 0.5f),
modifier = Modifier
.align(Alignment.TopStart)
.padding(12.dp)
)
}
}
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(12.dp))
.background(previewColor)
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier.weight(1f)
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)
) {
AppText(
text = theme.name,
style = MaterialTheme.typography.bodyLarge
style = MaterialTheme.typography.titleSmall,
maxLines = 1
)
val features = mutableListOf<String>()
features.add(
when (theme.data.appTheme) {
"0" -> "动态取色"
"12" -> "自定义颜色"
else -> "预设主题"
}
)
if (theme.data.enableBlur) features.add("模糊效果")
if (theme.data.useFloatingBottomBar) features.add("浮动底栏")
AppText(
text = features.joinToString(" · "),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(onClick = onEdit) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "编辑",
modifier = Modifier.size(20.dp)
)
}
IconButton(onClick = onExport) {
Icon(
imageVector = Icons.Default.Share,
contentDescription = "导出",
modifier = Modifier.size(20.dp)
)
}
IconButton(onClick = onDelete) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "删除",
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.error
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = onEdit, modifier = Modifier.size(32.dp)) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "编辑",
modifier = Modifier.size(18.dp)
)
}
IconButton(onClick = onExport, modifier = Modifier.size(32.dp)) {
Icon(
imageVector = Icons.Default.Share,
contentDescription = "导出",
modifier = Modifier.size(18.dp)
)
}
IconButton(onClick = onDelete, modifier = Modifier.size(32.dp)) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "删除",
modifier = Modifier.size(18.dp),
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
}
@@ -55,6 +55,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -240,6 +241,7 @@ fun MainScreen(
val haptic = LocalHapticFeedback.current
WideNavigationRailItem(
modifier = Modifier.testTag("nav_${destination.route}"),
railExpanded = navState.targetValue == WideNavigationRailValue.Expanded,
selected = selected,
onClick = {
@@ -308,6 +310,7 @@ fun MainScreen(
MainDestination.My -> ThemeConfig.navIconMy
}
AppNavigationBarItem(
modifier = Modifier.testTag("nav_${destination.route}"),
selected = selected,
onClick = {
coroutineScope.launch { pagerState.animateScrollToPage(index) }
@@ -5,6 +5,7 @@ import android.content.res.Configuration
import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.ReportDrawnWhen
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
@@ -81,6 +82,7 @@ import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@@ -145,6 +147,8 @@ fun BookshelfScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope()
ReportDrawnWhen { uiState.groups.isNotEmpty() }
val activeOverlay = uiState.activeOverlay
val showGroupMenu = activeOverlay == BookshelfOverlay.GroupMenu
val isEditMode = uiState.isEditMode
@@ -1215,6 +1219,7 @@ fun BookshelfPage(
state = gridState,
modifier = Modifier
.fillMaxSize()
.testTag("bookshelf_list")
.then(
with(sharedTransitionScope) {
if (this != null) Modifier.skipToLookaheadSize() else Modifier