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