[修复] 修复无法打开除文本之外的问题、修复在有在线封面时依旧显示文字的问题、修复主页面底部间距等界面问题

This commit is contained in:
HapeLee committed 2026-03-18 01:52:02 +08:00
1 parent c469cab5d6
commit 1f4a5fb01c
9 files changed
+134 -77

No files matched your search

@@ -8,12 +8,16 @@ import io.legado.app.help.glide.progress.ProgressResponseBody
import io.legado.app.help.http.CookieManager.cookieJarHeader import io.legado.app.help.http.CookieManager.cookieJarHeader
import io.legado.app.model.ReadManga import io.legado.app.model.ReadManga
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import okhttp3.Cache
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.CookieJar import okhttp3.CookieJar
import okhttp3.Credentials import okhttp3.Credentials
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import splitties.init.appCtx
import java.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -47,6 +51,26 @@ val cookieJar by lazy {
} }
} }
/**
* 强制缓存拦截器,对于图片资源,如果没有缓存头,则强制缓存30天
*/
private val cacheControlInterceptor = Interceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
val url = request.url.toString()
if (url.contains("image", true) || url.endsWith(".jpg") || url.endsWith(".png") || url.endsWith(
".webp"
) || url.endsWith(".jpeg")
) {
response.newBuilder()
.header("Cache-Control", "public, max-age=2592000")
.removeHeader("Pragma")
.build()
} else {
response
}
}
val okHttpClient: OkHttpClient by lazy { val okHttpClient: OkHttpClient by lazy {
val specs = arrayListOf( val specs = arrayListOf(
ConnectionSpec.MODERN_TLS, ConnectionSpec.MODERN_TLS,
@@ -54,7 +78,11 @@ val okHttpClient: OkHttpClient by lazy {
ConnectionSpec.CLEARTEXT ConnectionSpec.CLEARTEXT
) )
//TODO:自定义缓存大小
val cache = Cache(File(appCtx.cacheDir, "http_cache"), 100L * 1024L * 1024L)
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
.cache(cache)
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
@@ -77,9 +105,9 @@ val okHttpClient: OkHttpClient by lazy {
} }
builder.addHeader("Keep-Alive", "300") builder.addHeader("Keep-Alive", "300")
builder.addHeader("Connection", "Keep-Alive") builder.addHeader("Connection", "Keep-Alive")
builder.addHeader("Cache-Control", "no-cache")
chain.proceed(builder.build()) chain.proceed(builder.build())
} }
.addNetworkInterceptor(cacheControlInterceptor) // 添加强制缓存拦截器
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
var request = chain.request() var request = chain.request()
val enableCookieJar = request.header(cookieJarHeader) != null val enableCookieJar = request.header(cookieJarHeader) != null
@@ -1,9 +1,13 @@
package io.legado.app.ui.main package io.legado.app.ui.main
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -22,23 +26,26 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem
import androidx.compose.material3.WideNavigationRailValue
import androidx.compose.material3.rememberWideNavigationRailState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.mainConfig.MainConfig import io.legado.app.ui.config.mainConfig.MainConfig
import io.legado.app.ui.main.bookshelf.BookshelfScreen import io.legado.app.ui.main.bookshelf.BookshelfScreen
@@ -48,6 +55,7 @@ import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.regularHazeEffect import io.legado.app.ui.theme.regularHazeEffect
import io.legado.app.ui.widget.components.GlassDefaults import io.legado.app.ui.widget.components.GlassDefaults
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.startActivityForBook
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -78,27 +86,60 @@ fun MainScreen(
} }
val pagerState = rememberPagerState(initialPage = initialPage) { destinations.size } val pagerState = rememberPagerState(initialPage = initialPage) { destinations.size }
val navState = rememberWideNavigationRailState(
initialValue = if (MainConfig.navExtended)
WideNavigationRailValue.Expanded
else
WideNavigationRailValue.Collapsed
)
LaunchedEffect(navState.currentValue) {
MainConfig.navExtended =
navState.currentValue == WideNavigationRailValue.Expanded
}
Row(modifier = Modifier.fillMaxSize()) { Row(modifier = Modifier.fillMaxSize()) {
if (useRail && MainConfig.showBottomView) { if (useRail && MainConfig.showBottomView) {
NavigationRail( WideNavigationRail(
state = navState,
header = { header = {
IconButton(onClick = { MainConfig.navExtended = !MainConfig.navExtended }) { val expanded = navState.targetValue == WideNavigationRailValue.Expanded
Column {
IconButton(
modifier = Modifier.padding(start = 24.dp),
onClick = {
coroutineScope.launch {
if (expanded) navState.collapse()
else navState.expand()
}
}
) {
Icon( Icon(
if (MainConfig.navExtended) Icons.AutoMirrored.Filled.MenuOpen else Icons.Default.Menu, if (expanded)
Icons.AutoMirrored.Filled.MenuOpen
else
Icons.Default.Menu,
contentDescription = null contentDescription = null
) )
} }
Spacer(modifier = Modifier.height(8.dp))
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
modifier = Modifier.padding(start = 20.dp),
onClick = { context.startActivity<SearchActivity>() }, onClick = { context.startActivity<SearchActivity>() },
expanded = MainConfig.navExtended, expanded = expanded,
icon = { Icon(Icons.Default.Search, contentDescription = null) }, icon = { Icon(Icons.Default.Search, contentDescription = null) },
text = { Text(text = stringResource(R.string.search)) } text = { Text(stringResource(R.string.search)) }
) )
} }
}
) { ) {
destinations.forEachIndexed { index, destination -> destinations.forEachIndexed { index, destination ->
val selected = pagerState.currentPage == index val selected = pagerState.currentPage == index
NavigationRailItem( WideNavigationRailItem(
railExpanded = navState.targetValue == WideNavigationRailValue.Expanded,
selected = selected, selected = selected,
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
@@ -157,14 +198,12 @@ fun MainScreen(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
userScrollEnabled = true, userScrollEnabled = true,
beyondViewportPageCount = 1 beyondViewportPageCount = 3
) { page -> ) { page ->
when (destinations[page]) { when (destinations[page]) {
MainDestination.Bookshelf -> BookshelfScreen( MainDestination.Bookshelf -> BookshelfScreen(
onBookClick = { book -> onBookClick = { book ->
context.startActivity<ReadBookActivity> { context.startActivityForBook(book)
putExtra("bookUrl", book.bookUrl)
}
}, },
onBookLongClick = { book -> onBookLongClick = { book ->
context.startActivity<BookInfoActivity> { context.startActivity<BookInfoActivity> {
@@ -71,12 +71,14 @@ fun BookshelfItem(
val coverModifier = if (coverShadow) { val coverModifier = if (coverShadow) {
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.shadow(4.dp, RoundedCornerShape(4.dp)) .shadow(4.dp, MaterialTheme.shapes.extraSmall)
} else { } else {
Modifier.fillMaxWidth() Modifier.fillMaxWidth()
} }
Box { Box(
modifier = Modifier.clip(MaterialTheme.shapes.extraSmall)
) {
cover(coverModifier) cover(coverModifier)
if (isCompact) { if (isCompact) {
Text( Text(
@@ -135,7 +137,7 @@ fun BookshelfItem(
) { ) {
cover( cover(
Modifier Modifier
.width(if (!isCompact) 70.dp else 55.dp) .width(if (!isCompact) 80.dp else 56.dp)
.padding(end = 12.dp) .padding(end = 12.dp)
) )
Column( Column(
@@ -8,11 +8,9 @@ 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.LazyColumn
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.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.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
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -254,7 +252,7 @@ fun BookshelfScreen(
text = group.groupName, text = group.groupName,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 4.dp), modifier = Modifier.padding(horizontal = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -291,16 +289,20 @@ fun BookshelfScreen(
} }
) { ) {
if (bookGroupStyle == 2 && isInFolderRoot) { if (bookGroupStyle == 2 && isInFolderRoot) {
LazyVerticalGrid(
if (uiState.bookshelfLayoutMode == 0) { columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = 56.dp bottom = 120.dp,
) start = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp,
end = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp
),
verticalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp)
) { ) {
items(uiState.groups) { group -> items(uiState.groups) { group ->
if (uiState.bookshelfLayoutMode == 0) {
BookGroupItemList( BookGroupItemList(
group = group, group = group,
previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(), previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
@@ -317,23 +319,7 @@ fun BookshelfScreen(
}, },
onLongClick = { showGroupManageSheet = true } onLongClick = { showGroupManageSheet = true }
) )
}
}
} else { } else {
LazyVerticalGrid(
columns = GridCells.Fixed(uiState.bookshelfLayoutGrid),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding() + 16.dp,
start = 12.dp,
end = 12.dp
),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(uiState.groups) { group ->
BookGroupItemGrid( BookGroupItemGrid(
group = group, group = group,
previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(), previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
@@ -358,7 +344,7 @@ fun BookshelfScreen(
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
beyondViewportPageCount = 1, beyondViewportPageCount = 3,
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it } key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
) { pageIndex -> ) { pageIndex ->
val group = uiState.groups.getOrNull(pageIndex) val group = uiState.groups.getOrNull(pageIndex)
@@ -447,37 +433,17 @@ fun BookshelfPage(
onBookClick: (Book) -> Unit, onBookClick: (Book) -> Unit,
onBookLongClick: (Book) -> Unit onBookLongClick: (Book) -> Unit
) { ) {
if (uiState.bookshelfLayoutMode == 0) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding(),
bottom = 56.dp
)
) {
items(books, key = { it.bookUrl }) { book ->
BookItem(
book = book,
layoutMode = uiState.bookshelfLayoutMode,
isCompact = uiState.bookshelfLayoutCompact,
isUpdating = uiState.updatingBooks.contains(book.bookUrl),
onClick = { onBookClick(book) },
onLongClick = { onBookLongClick(book) }
)
}
}
} else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(uiState.bookshelfLayoutGrid), columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding() + 16.dp, bottom = 120.dp,
start = 12.dp, start = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp,
end = 12.dp end = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp
), ),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp)
) { ) {
items(books, key = { it.bookUrl }) { book -> items(books, key = { it.bookUrl }) { book ->
BookItem( BookItem(
@@ -494,7 +460,6 @@ fun BookshelfPage(
) )
} }
} }
}
} }
@Composable @Composable
@@ -105,7 +105,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
val scrollTrigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1) val scrollTrigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val groupsFlow: StateFlow<List<BookGroup>> = appDb.bookGroupDao.show.asFlow() val groupsFlow: StateFlow<List<BookGroup>> = appDb.bookGroupDao.show.asFlow()
.map { it ?: emptyList() } .map { it }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
private val allBooksFlow = appDb.bookDao.flowAll().flowOn(Dispatchers.Default) private val allBooksFlow = appDb.bookDao.flowAll().flowOn(Dispatchers.Default)
@@ -166,7 +166,7 @@ fun ExploreScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues( contentPadding = PaddingValues(
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding() + 16.dp bottom = 120.dp
) )
) { ) {
uiState.items.forEach { item -> uiState.items.forEach { item ->
@@ -1,8 +1,10 @@
package io.legado.app.ui.main.my package io.legado.app.ui.main.my
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
@@ -90,9 +92,13 @@ fun MyScreen(
) { padding -> ) { padding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(padding) .padding(
.verticalScroll(rememberScrollState()) top = padding.calculateTopPadding()
)
.verticalScroll(rememberScrollState()),
//TODO:暂时这样解决,改天加模糊
) { ) {
SplicedColumnGroup( SplicedColumnGroup(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
title = "" title = ""
@@ -271,6 +277,8 @@ fun MyScreen(
} }
) )
} }
Spacer(Modifier.height(120.dp))
} }
} }
} }
@@ -165,7 +165,7 @@ fun RssScreen(
start = 12.dp, start = 12.dp,
end = 12.dp, end = 12.dp,
top = paddingValues.calculateTopPadding() + 8.dp, top = paddingValues.calculateTopPadding() + 8.dp,
bottom = paddingValues.calculateBottomPadding() + 12.dp bottom = 120.dp
), ),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
@@ -22,7 +22,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -64,6 +67,8 @@ fun BookCover(
BookCover.getRandomDefaultDrawable(seed = name ?: author ?: path ?: "") BookCover.getRandomDefaultDrawable(seed = name ?: author ?: path ?: "")
} }
var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
Box( Box(
modifier = modifier modifier = modifier
.aspectRatio(5f / 7f) .aspectRatio(5f / 7f)
@@ -98,16 +103,26 @@ fun BookCover(
imageLoader = koinInject(), imageLoader = koinInject(),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onSuccess = { onLoadFinish?.invoke() }, onSuccess = {
onError = { onLoadFinish?.invoke() } if (finalPath != null) {
isOnlineCoverLoaded = true
}
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onLoadFinish?.invoke()
}
) )
// 2. 文字叠加层(始终尝试渲染,由 CoverConfig 内部控制具体显示逻辑) // 2. 文字叠加层(当没有在线封面或者加载失败时显示)
if (!isOnlineCoverLoaded) {
CoverTextOverlay( CoverTextOverlay(
name = name, name = name,
author = author, author = author,
isNight = isNight isNight = isNight
) )
}
// 3. 角标 (Badge) // 3. 角标 (Badge)
if (badgeContent != null) { if (badgeContent != null) {