diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 35230e14a..30a8e1c5d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -90,7 +90,15 @@ "WebFetch(domain:api.github.com)", "Bash(javap -public /tmp/classes.jar!top/yukonga/miuix/kmp/basic/NavigationBarKt.class)", "Bash(javap -public -cp /tmp/classes.jar top.yukonga.miuix.kmp.basic.NavigationBarKt)", - "PowerShell(Join-Path *)" + "PowerShell(Join-Path *)", + "Bash(dir /s /b \"C:\\\\Users\\\\*\\\\.gradle\\\\caches\")", + "Bash(findstr /i \"material3\")", + "Bash(findstr /i \"sources\")", + "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\")", + "Bash(findstr /i \"material3.*sources\")", + "WebFetch(domain:android.googlesource.com)", + "Read(//c/Users//**)", + "Read(//c/Users/**)" ] } } diff --git a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt index ecabe0531..5c48ec84f 100644 --- a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt @@ -3,6 +3,9 @@ package io.legado.app.ui.book.manage import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -22,10 +25,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.MenuOpen import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Bookmarks -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Info @@ -38,18 +39,9 @@ import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.Stop import androidx.compose.material.icons.filled.Upload import androidx.compose.material3.Checkbox -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.FloatingActionButtonMenu -import androidx.compose.material3.FloatingActionButtonMenuItem -import androidx.compose.material3.Icon import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.material3.ToggleFloatingActionButton -import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon -import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf @@ -60,7 +52,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -81,13 +72,18 @@ import io.legado.app.ui.book.changesource.ChangeSourceMigrationOptionsSheet import io.legado.app.ui.book.info.ChangeSourceSheet import io.legado.app.ui.book.info.GroupSelectSheet import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.theme.LegadoTheme.composeEngine +import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding +import io.legado.app.ui.widget.components.AppFloatingActionButtonMenu import io.legado.app.ui.widget.components.AppTextField +import io.legado.app.ui.widget.components.FabMenuItem import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.SmallTonalTextButton +import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.SelectionItemCard @@ -119,11 +115,6 @@ import org.koin.androidx.compose.koinViewModel import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState -data class BookshelfManageFabAction( - val icon: androidx.compose.ui.graphics.vector.ImageVector, - val label: String, - val action: () -> Unit -) private data class BookshelfManageListState( override val items: List = emptyList(), @@ -150,7 +141,6 @@ fun BookshelfManageRouteScreen( ) } -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun BookshelfManageScreen( viewModel: BookshelfManageScreenViewModel, @@ -390,20 +380,20 @@ private fun BookshelfManageScreen( return if (targetBooks.all { it.group == firstGroup }) firstGroup else 0L } val fabItems = listOf( - BookshelfManageFabAction( + FabMenuItem( Icons.Default.SelectAll, stringResource(R.string.select_all) ) { selectedBookUrls = filteredBooks.mapTo(hashSetOf()) { it.bookUrl } }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Refresh, stringResource(R.string.revert_selection) ) { val filteredUrls = filteredBooks.map { it.bookUrl }.toSet() selectedBookUrls = (selectedBookUrls - filteredUrls) + (filteredUrls - selectedBookUrls) }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Download, "缓存选中" ) { @@ -411,7 +401,7 @@ private fun BookshelfManageScreen( showBatchDownloadConfirmDialog = true } }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Refresh, "批量换源" ) { @@ -419,7 +409,7 @@ private fun BookshelfManageScreen( showBatchSourcePickerSheet = true } }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Bookmarks, stringResource(R.string.move_to_group) ) { @@ -429,20 +419,20 @@ private fun BookshelfManageScreen( showGroupSelectSheet = true } }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Upload, "导出选中" ) { exportSelected() }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Delete, stringResource(R.string.clear_cache) ) { viewModel.dispatch(BookshelfManageScreenIntent.ClearCachesForBooks(selectedBookUrls)) clearSelection() }, - BookshelfManageFabAction( + FabMenuItem( Icons.Default.Delete, stringResource(R.string.delete) ) { @@ -620,44 +610,14 @@ private fun BookshelfManageScreen( ) }, floatingActionButton = { - FloatingActionButtonMenu( + AppFloatingActionButtonMenu( modifier = Modifier.offset(x = 16.dp, y = 16.dp), expanded = fabMenuExpanded, - button = { - ToggleFloatingActionButton( - modifier = Modifier - .animateFloatingActionButton( - visible = true, - alignment = Alignment.BottomEnd, - ) - .focusRequester(focusRequester), - checked = fabMenuExpanded, - onCheckedChange = { fabMenuExpanded = !fabMenuExpanded }, - ) { - val imageVector by remember { - derivedStateOf { - if (checkedProgress > 0.5f) Icons.Filled.Close else Icons.AutoMirrored.Filled.MenuOpen - } - } - Icon( - imageVector = imageVector, - contentDescription = "Menu", - modifier = Modifier.animateIcon({ checkedProgress }), - ) - } - } - ) { - fabItems.forEach { (icon, label, action) -> - FloatingActionButtonMenuItem( - onClick = { - action() - fabMenuExpanded = false - }, - icon = { Icon(icon, contentDescription = null) }, - text = { Text(text = label) } - ) - } - } + onExpandedChange = { fabMenuExpanded = it }, + items = fabItems, + visible = true, + focusRequester = focusRequester + ) } ) { paddingValues -> val renderVersion by rememberUpdatedState(state.cacheVersion) @@ -681,6 +641,15 @@ private fun BookshelfManageScreen( stringResource(R.string.cache_download_failed, it) } val isSelected = selectedBookUrls.contains(book.bookUrl) + val isMiuix = ThemeResolver.isMiuixEngine(composeEngine) + val animatedContainerColor by animateColorAsState( + targetValue = if (isSelected) + LegadoTheme.colorScheme.secondaryContainer + else + if (isMiuix) LegadoTheme.colorScheme.surfaceContainer else LegadoTheme.colorScheme.surfaceContainerLow, + animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing), + label = "CardColor" + ) val exportMsg = remember(renderVersion, book.bookUrl) { ExportBookService.exportMsg[book.bookUrl] } @@ -689,7 +658,7 @@ private fun BookshelfManageScreen( key = book.bookUrl, enabled = canReorderBooks ) { - NormalCard( + GlassCard( modifier = Modifier .fillMaxWidth() .then( @@ -701,11 +670,7 @@ private fun BookshelfManageScreen( ), onClick = { toggleBookSelection(book) }, onLongClick = { toggleBookSelection(book) }, - containerColor = if (isSelected) { - LegadoTheme.colorScheme.surfaceContainerHigh - } else { - LegadoTheme.colorScheme.surfaceContainerLow - } + containerColor = animatedContainerColor ) { Column( modifier = Modifier diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt index ebe4e10f1..21cb8004e 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt @@ -36,10 +36,8 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.FormatListBulleted -import androidx.compose.material.icons.automirrored.filled.MenuOpen import androidx.compose.material.icons.filled.BookmarkAdd import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.DownloadForOffline import androidx.compose.material.icons.filled.ExpandMore @@ -52,16 +50,10 @@ import androidx.compose.material.icons.outlined.DownloadForOffline import androidx.compose.material.icons.rounded.LocationOn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.FloatingActionButtonMenu -import androidx.compose.material3.FloatingActionButtonMenuItem import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.ToggleFloatingActionButton -import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon -import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -76,7 +68,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -95,8 +86,10 @@ import io.legado.app.ui.theme.adaptiveContentPaddingOnlyVertical import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.widget.CollapsibleHeader import io.legado.app.ui.widget.components.ActionItem +import io.legado.app.ui.widget.components.AppFloatingActionButtonMenu import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.EmptyMessage +import io.legado.app.ui.widget.components.FabMenuItem import io.legado.app.ui.widget.components.SelectionBottomBar import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet import io.legado.app.ui.widget.components.bookmark.BookmarkItem @@ -215,7 +208,7 @@ fun TocScreen( val fabItems = remember(state.items) { listOf( - FabAction(Icons.Default.LocationOn, "定位至当前阅读") { + FabMenuItem(Icons.Default.LocationOn, "定位至当前阅读") { scope.launch { val target = state.items.indexOfFirst { it.isDur } if (target != -1) { @@ -226,13 +219,13 @@ fun TocScreen( } } }, - FabAction(Icons.Default.VerticalAlignTop, "移至顶部") { + FabMenuItem(Icons.Default.VerticalAlignTop, "移至顶部") { scope.launch { listState.animateScrollToItem(0) } }, - FabAction(Icons.Default.VerticalAlignBottom, "移至底部") { + FabMenuItem(Icons.Default.VerticalAlignBottom, "移至底部") { scope.launch { listState.animateScrollToItem(state.items.size) } }, - FabAction(Icons.Default.DownloadForOffline, "下载全部") { + FabMenuItem(Icons.Default.DownloadForOffline, "下载全部") { viewModel.downloadAll() } ) @@ -509,45 +502,15 @@ fun TocScreen( ) }, floatingActionButton = { - FloatingActionButtonMenu( + AppFloatingActionButtonMenu( modifier = Modifier .offset(x = 16.dp, y = 16.dp), expanded = fabMenuExpanded, - button = { - ToggleFloatingActionButton( - modifier = Modifier - .animateFloatingActionButton( - visible = shouldShowFab, - alignment = Alignment.BottomEnd, - ) - .focusRequester(focusRequester), - checked = fabMenuExpanded, - onCheckedChange = { fabMenuExpanded = !fabMenuExpanded }, - ) { - val imageVector by remember { - derivedStateOf { - if (checkedProgress > 0.5f) Icons.Filled.Close else Icons.AutoMirrored.Filled.MenuOpen - } - } - Icon( - imageVector = imageVector, - contentDescription = "Menu", - modifier = Modifier.animateIcon({ checkedProgress }), - ) - } - } - ) { - fabItems.forEach { (icon, label, action) -> - FloatingActionButtonMenuItem( - onClick = { - action() - fabMenuExpanded = false - }, - icon = { Icon(icon, contentDescription = null) }, - text = { Text(text = label) } - ) - } - } + onExpandedChange = { fabMenuExpanded = it }, + items = fabItems, + visible = shouldShowFab, + focusRequester = focusRequester + ) } ) { padding -> Box(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt index c78908c07..3460181ea 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt @@ -4,7 +4,7 @@ import android.app.Application import android.net.Uri import androidx.compose.runtime.Immutable import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.graphics.vector.ImageVector + import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import io.legado.app.base.BaseRuleViewModel @@ -109,8 +109,6 @@ private data class TitleCacheKey( val chapterCount: Int ) -data class FabAction(val icon: ImageVector, val label: String, val action: () -> Unit) - @OptIn(ExperimentalCoroutinesApi::class) class TocViewModel( application: Application, diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt index 750840969..3c4196125 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesScreen.kt @@ -219,7 +219,9 @@ fun RssFavoritesScreen( path = rssStar.image, sourceOrigin = rssStar.origin, contentScale = ContentScale.Crop, - modifier = Modifier.size(54.dp) + modifier = Modifier + .padding(vertical = 12.dp) + .size(54.dp) ) } } else null, diff --git a/app/src/main/java/io/legado/app/ui/rss/read/RssReadRouteScreen.kt b/app/src/main/java/io/legado/app/ui/rss/read/RssReadRouteScreen.kt index 3b54af471..bc922e627 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/RssReadRouteScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/RssReadRouteScreen.kt @@ -3,24 +3,15 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint -import android.net.Uri -import android.net.http.SslError -import android.os.SystemClock import android.view.View import android.view.ViewGroup -import android.webkit.JavascriptInterface -import android.webkit.SslErrorHandler -import android.webkit.URLUtil import android.webkit.WebChromeClient -import android.webkit.WebResourceRequest -import android.webkit.WebSettings import android.webkit.WebView -import android.webkit.WebViewClient import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalActivity -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -28,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.clickable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.filled.Check @@ -42,10 +32,6 @@ import androidx.compose.material.icons.filled.Stop import androidx.compose.material.icons.filled.VolumeUp import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -59,40 +45,30 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex -import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import com.script.rhino.runScriptWithContext import io.legado.app.R import io.legado.app.constant.AppConst -import io.legado.app.constant.AppLog import io.legado.app.help.config.AppConfig import io.legado.app.help.http.CookieManager -import io.legado.app.lib.dialogs.SelectItem -import io.legado.app.lib.dialogs.selector -import io.legado.app.model.Download -import io.legado.app.ui.association.OnLineImportActivity import io.legado.app.ui.config.otherConfig.OtherConfig import io.legado.app.ui.login.SourceLoginActivity +import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow -import io.legado.app.ui.widget.components.button.MediumIconButton -import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.button.SmallIconButton -import io.legado.app.ui.widget.components.topbar.TopBarActionButton -import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton import io.legado.app.ui.widget.components.menuItem.MenuItemIcon import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet +import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator +import io.legado.app.ui.widget.components.topbar.GlassTopAppBar +import io.legado.app.ui.widget.components.topbar.TopBarActionButton +import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton import io.legado.app.utils.NetworkUtils -import io.legado.app.utils.isTrue import io.legado.app.utils.keepScreenOn -import io.legado.app.utils.longSnackbar import io.legado.app.utils.openUrl import io.legado.app.utils.setDarkeningAllowed import io.legado.app.utils.share @@ -101,7 +77,6 @@ import io.legado.app.utils.toastOnUi import io.legado.app.utils.toggleSystemBar import org.apache.commons.text.StringEscapeUtils import org.jsoup.Jsoup -import java.net.URLDecoder import org.koin.androidx.compose.koinViewModel @SuppressLint("SetJavaScriptEnabled") @@ -140,7 +115,6 @@ fun RssReadRouteScreen( val content by viewModel.contentState.collectAsStateWithLifecycle() val analyzeUrl by viewModel.urlState.collectAsStateWithLifecycle() - val rssStar by viewModel.rssStarState.collectAsStateWithLifecycle() val isSpeaking by viewModel.isSpeakingState.collectAsStateWithLifecycle() val fallbackUserAgent = OtherConfig.userAgent @@ -175,19 +149,29 @@ fun RssReadRouteScreen( val article = viewModel.rssArticle ?: return@LaunchedEffect val url = NetworkUtils.getAbsoluteURL(article.origin, article.link) val html = viewModel.clHtml(body) - if (viewModel.rssSource?.loadWithBaseUrl == true) { - currentWebView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url) - } else { - currentWebView.loadDataWithBaseURL(null, html, "text/html;charset=utf-8", "utf-8", url) + if (currentWebView.url != url) { + if (viewModel.rssSource?.loadWithBaseUrl == true) { + currentWebView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url) + } else { + currentWebView.loadDataWithBaseURL( + null, + html, + "text/html;charset=utf-8", + "utf-8", + url + ) + } } } LaunchedEffect(analyzeUrl, webView) { val url = analyzeUrl ?: return@LaunchedEffect val currentWebView = webView ?: return@LaunchedEffect - CookieManager.applyToWebView(url.url) - currentWebView.settings.userAgentString = url.getUserAgent() - currentWebView.loadUrl(url.url, url.headerMap) + if (currentWebView.url != url.url) { + CookieManager.applyToWebView(url.url) + currentWebView.settings.userAgentString = url.getUserAgent() + currentWebView.loadUrl(url.url, url.headerMap) + } } BackHandler { @@ -205,17 +189,19 @@ fun RssReadRouteScreen( } } + val isNight = isSystemInDarkTheme() + + LaunchedEffect(isNight, webView) { + val currentWebView = webView ?: return@LaunchedEffect + currentWebView.settings.setDarkeningAllowed(isNight) + } + Box(modifier = Modifier.fillMaxSize()) { - Scaffold( + AppScaffold( + disableHazeSource = true, topBar = { - TopAppBar( - title = { - Text( - text = pageTitle.ifBlank { defaultTopBarTitle }, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - }, + GlassTopAppBar( + title = pageTitle.ifBlank { defaultTopBarTitle }, navigationIcon = { TopBarNavigationButton(onClick = onBackClick) }, @@ -236,11 +222,11 @@ fun RssReadRouteScreen( } ) Box { - TopBarActionButton( - imageVector = Icons.Default.MoreVert, - contentDescription = "Menu", - onClick = { showMenu = true } - ) + TopBarActionButton( + imageVector = Icons.Default.MoreVert, + contentDescription = "Menu", + onClick = { showMenu = true } + ) RoundDropdownMenu( expanded = showMenu, onDismissRequest = { showMenu = false } @@ -256,7 +242,7 @@ fun RssReadRouteScreen( context.share(it.link) } ?: context.toastOnUi(R.string.null_url) } - ) + ) RoundDropdownMenuItem( text = if (isSpeaking) stringResource(R.string.aloud_stop) else stringResource( R.string.read_aloud @@ -276,21 +262,21 @@ fun RssReadRouteScreen( Jsoup.parse(html).text() ) } - } + } } ) if (!viewModel.rssSource?.loginUrl.isNullOrBlank()) { - RoundDropdownMenuItem( - text = stringResource(R.string.login), - leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, - onClick = { - dismiss() - context.startActivity { - putExtra("type", "rssSource") - putExtra("key", viewModel.rssSource?.sourceUrl) - } + RoundDropdownMenuItem( + text = stringResource(R.string.login), + leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, + onClick = { + dismiss() + context.startActivity { + putExtra("type", "rssSource") + putExtra("key", viewModel.rssSource?.sourceUrl) } - ) + } + ) } RoundDropdownMenuItem( text = stringResource(R.string.redirect_policy), @@ -301,8 +287,9 @@ fun RssReadRouteScreen( leadingIcon = { MenuItemIcon(Icons.Default.OpenInBrowser) }, onClick = { dismiss() - webView?.url?.let { context.openUrl(it) } ?: context.toastOnUi("url null") - } + webView?.url?.let { context.openUrl(it) } + ?: context.toastOnUi("url null") + } ) } RoundDropdownMenu( @@ -310,27 +297,30 @@ fun RssReadRouteScreen( onDismissRequest = { showRedirectMenu = false } ) { dismiss -> RedirectPolicy.entries.forEach { policy -> - RoundDropdownMenuItem( - text = policy.title(), - onClick = { - dismiss() - showMenu = false - viewModel.rssSource?.let { source -> - viewModel.updateRssSourceRedirectPolicy(source.sourceUrl, policy.name) - redirectPolicy = policy - } - context.toastOnUi("重定向策略已更新") - }, - trailingIcon = { - if (policy == redirectPolicy) { - Icon(Icons.Default.Check, null) - } + RoundDropdownMenuItem( + text = policy.title(), + onClick = { + dismiss() + showMenu = false + viewModel.rssSource?.let { source -> + viewModel.updateRssSourceRedirectPolicy( + source.sourceUrl, + policy.name + ) + redirectPolicy = policy } - ) - } + context.toastOnUi("重定向策略已更新") + }, + trailingIcon = { + if (policy == redirectPolicy) { + Icon(Icons.Default.Check, null) + } + } + ) } } } + } ) } ) { paddingValues -> @@ -369,18 +359,18 @@ fun RssReadRouteScreen( } }, onHideCustomView = { hideCustomView() } - ) ) - } + ) + } ) if (webProgress in 0..99) { AppLinearProgressIndicator( progress = webProgress / 100f, modifier = Modifier.fillMaxWidth() ) - } } } + } customView?.let { view -> key(view) { diff --git a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt index 937fbd054..353c4c21f 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt @@ -29,6 +29,7 @@ class VisibleWebView( private var lastSelectedText: String = "" init { + setBackgroundColor(0) settings.javaScriptEnabled = true settings.domStorageEnabled = true diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt index 6b4bb65fd..249cba946 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppFloatingActionButton.kt @@ -1,23 +1,55 @@ package io.legado.app.ui.widget.components +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.MenuOpen +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonMenu +import androidx.compose.material3.FloatingActionButtonMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.Text +import androidx.compose.material3.ToggleFloatingActionButton +import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.animateFloatingActionButton import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.ThemeResolver -import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.text.AppText +import top.yukonga.miuix.kmp.basic.Button +import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.basic.FloatingActionButton as MiuixFloatingActionButton import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon +import top.yukonga.miuix.kmp.basic.Text as MiuixText @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -85,3 +117,126 @@ fun AppFloatingActionButton( } } } + +data class FabMenuItem( + val icon: ImageVector, + val label: String, + val action: () -> Unit +) + +@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) +@Composable +fun AppFloatingActionButtonMenu( + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, + items: List, + modifier: Modifier = Modifier, + visible: Boolean = true, + focusRequester: FocusRequester = remember { FocusRequester() } +) { + val isMiuix = ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine) + + if (isMiuix) { + Column( + modifier = modifier.padding(horizontal = 16.dp, vertical = 24.dp), + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items.forEachIndexed { index, (icon, label, action) -> + AnimatedVisibility( + visible = expanded, + enter = fadeIn(tween(delayMillis = index * 40)) + + scaleIn(tween(delayMillis = index * 40), initialScale = 0.8f), + exit = fadeOut(tween(80)) + scaleOut(tween(80), targetScale = 0.8f) + ) { + Button( + onClick = { + action() + onExpandedChange(false) + } + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + MiuixIcon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + MiuixText( + text = label, + style = MiuixTheme.textStyles.subtitle + ) + } + } + } + } + + AnimatedVisibility( + visible = visible, + enter = fadeIn() + scaleIn(initialScale = 0.8f), + exit = fadeOut() + scaleOut(targetScale = 0.8f) + ) { + MiuixFloatingActionButton( + onClick = { onExpandedChange(!expanded) } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + MiuixIcon( + imageVector = if (expanded) Icons.Filled.Close + else Icons.AutoMirrored.Filled.MenuOpen, + contentDescription = "Menu", + tint = Color.White + ) + Spacer(modifier = Modifier.width(8.dp)) + Text("菜单", color = Color.White) + } + } + } + } + } else { + FloatingActionButtonMenu( + modifier = modifier, + expanded = expanded, + button = { + ToggleFloatingActionButton( + modifier = Modifier + .animateFloatingActionButton( + visible = visible, + alignment = Alignment.BottomEnd, + ) + .focusRequester(focusRequester), + checked = expanded, + onCheckedChange = { onExpandedChange(!expanded) }, + ) { + val imageVector by remember { + derivedStateOf { + if (checkedProgress > 0.5f) Icons.Filled.Close + else Icons.AutoMirrored.Filled.MenuOpen + } + } + Icon( + imageVector = imageVector, + contentDescription = "Menu", + modifier = Modifier.animateIcon({ checkedProgress }), + ) + } + } + ) { + items.forEach { (icon, label, action) -> + FloatingActionButtonMenuItem( + onClick = { + action() + onExpandedChange(false) + }, + icon = { Icon(icon, contentDescription = null) }, + text = { Text(text = label) } + ) + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt index b95f9cf35..506270474 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt @@ -17,7 +17,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil.compose.AsyncImage @@ -44,6 +43,7 @@ fun AppScaffold( contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface), contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, alwaysDrawBehindBars: Boolean = false, + disableHazeSource: Boolean = false, content: @Composable (PaddingValues) -> Unit ) { val isDark = isSystemInDarkTheme() @@ -97,7 +97,10 @@ fun AppScaffold( Box( modifier = Modifier .fillMaxSize() - .responsiveHazeSource(hazeState) + .then( + if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState) + else Modifier + ) .then( if (contentDrawsBehindBars) Modifier else Modifier.padding(scaffoldPadding) @@ -136,7 +139,10 @@ fun AppScaffold( Box( modifier = Modifier .fillMaxSize() - .responsiveHazeSource(hazeState) + .then( + if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState) + else Modifier + ) .then( if (contentDrawsBehindBars) Modifier else Modifier.padding(scaffoldPadding) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt index 81d010049..44531eec1 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/topbar/DynamicTopAppBar.kt @@ -22,9 +22,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.widget.components.SearchBar -import io.legado.app.ui.widget.components.icon.AppIcon -import io.legado.app.ui.widget.components.topbar.TopBarActionButton -import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.list.ListUiState import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu @@ -118,12 +115,6 @@ fun DynamicTopAppBar( onQueryChange = onSearchQueryChange, onSearch = onSearchSubmit, placeholder = searchPlaceholder, - leadingIcon = { - AppIcon( - imageVector = searchLeadingIcon, - contentDescription = null - ) - }, trailingIcon = searchTrailingIcon, dropdownMenu = searchDropdownMenu ) diff --git a/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassTopAppBar.kt b/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassTopAppBar.kt new file mode 100644 index 000000000..89bddce65 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/components/topbar/GlassTopAppBar.kt @@ -0,0 +1,74 @@ +package io.legado.app.ui.widget.components.topbar + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.theme.LocalHazeState +import io.legado.app.ui.theme.ThemeResolver +import io.legado.app.ui.theme.responsiveHazeEffect +import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun GlassTopAppBar( + title: String, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, + actions: @Composable RowScope.() -> Unit = {}, +) { + val hazeState = LocalHazeState.current + val composeEngine = LegadoTheme.composeEngine + val isMiuix = ThemeResolver.isMiuixEngine(composeEngine) + + val containerColor = if (!isMiuix) { + GlassTopAppBarDefaults.containerColor() + } else { + GlassTopAppBarDefaults.getMiuixAppBarColor() + } + + val finalModifier = if (hazeState != null) { + modifier + .background(color = containerColor) + .responsiveHazeEffect(state = hazeState) + } else { + modifier.background(color = containerColor) + } + + Column(modifier = finalModifier) { + if (isMiuix) { + MiuixTopAppBar( + title = title, + navigationIcon = navigationIcon, + actions = actions, + color = Color.Transparent + ) + } else { + TopAppBar( + title = { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge + ) + }, + navigationIcon = navigationIcon, + actions = actions, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ) + ) + } + } +} diff --git a/app/src/main/java/io/legado/app/utils/ConfigurationExtensions.kt b/app/src/main/java/io/legado/app/utils/ConfigurationExtensions.kt index 186e8ae69..ebb9a7c71 100644 --- a/app/src/main/java/io/legado/app/utils/ConfigurationExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ConfigurationExtensions.kt @@ -3,9 +3,9 @@ package io.legado.app.utils import android.content.res.Configuration -import android.content.res.Resources +import splitties.init.appCtx -val sysConfiguration: Configuration = Resources.getSystem().configuration +val sysConfiguration: Configuration get() = appCtx.resources.configuration val Configuration.isNightMode: Boolean get() {