代码优化
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user