界面优化

This commit is contained in:
HapeLee committed 2026-05-17 04:08:40 +08:00
1 parent 139dd76f3e
commit 1ac5f6549b
12 files changed
+377 -224

No files matched your search

+9 -1
View File
@@ -90,7 +90,15 @@
"WebFetch(domain:api.github.com)", "WebFetch(domain:api.github.com)",
"Bash(javap -public /tmp/classes.jar!top/yukonga/miuix/kmp/basic/NavigationBarKt.class)", "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)", "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/**)"
] ]
} }
} }
@@ -3,6 +3,9 @@ package io.legado.app.ui.book.manage
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts 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.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons 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.Add
import androidx.compose.material.icons.filled.Bookmarks 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.Delete
import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Info 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.Stop
import androidx.compose.material.icons.filled.Upload import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.Checkbox 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.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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -60,7 +52,6 @@ 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.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
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.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.ChangeSourceSheet
import io.legado.app.ui.book.info.GroupSelectSheet import io.legado.app.ui.book.info.GroupSelectSheet
import io.legado.app.ui.theme.LegadoTheme 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.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveHorizontalPadding 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.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.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.MediumIconButton 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.SmallTonalIconButton
import io.legado.app.ui.widget.components.button.SmallTonalTextButton 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.NormalCard
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.card.SelectionItemCard 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.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState 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( private data class BookshelfManageListState(
override val items: List<Book> = emptyList(), override val items: List<Book> = emptyList(),
@@ -150,7 +141,6 @@ fun BookshelfManageRouteScreen(
) )
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
private fun BookshelfManageScreen( private fun BookshelfManageScreen(
viewModel: BookshelfManageScreenViewModel, viewModel: BookshelfManageScreenViewModel,
@@ -390,20 +380,20 @@ private fun BookshelfManageScreen(
return if (targetBooks.all { it.group == firstGroup }) firstGroup else 0L return if (targetBooks.all { it.group == firstGroup }) firstGroup else 0L
} }
val fabItems = listOf( val fabItems = listOf(
BookshelfManageFabAction( FabMenuItem(
Icons.Default.SelectAll, Icons.Default.SelectAll,
stringResource(R.string.select_all) stringResource(R.string.select_all)
) { ) {
selectedBookUrls = filteredBooks.mapTo(hashSetOf()) { it.bookUrl } selectedBookUrls = filteredBooks.mapTo(hashSetOf()) { it.bookUrl }
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Refresh, Icons.Default.Refresh,
stringResource(R.string.revert_selection) stringResource(R.string.revert_selection)
) { ) {
val filteredUrls = filteredBooks.map { it.bookUrl }.toSet() val filteredUrls = filteredBooks.map { it.bookUrl }.toSet()
selectedBookUrls = (selectedBookUrls - filteredUrls) + (filteredUrls - selectedBookUrls) selectedBookUrls = (selectedBookUrls - filteredUrls) + (filteredUrls - selectedBookUrls)
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Download, Icons.Default.Download,
"缓存选中" "缓存选中"
) { ) {
@@ -411,7 +401,7 @@ private fun BookshelfManageScreen(
showBatchDownloadConfirmDialog = true showBatchDownloadConfirmDialog = true
} }
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Refresh, Icons.Default.Refresh,
"批量换源" "批量换源"
) { ) {
@@ -419,7 +409,7 @@ private fun BookshelfManageScreen(
showBatchSourcePickerSheet = true showBatchSourcePickerSheet = true
} }
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Bookmarks, Icons.Default.Bookmarks,
stringResource(R.string.move_to_group) stringResource(R.string.move_to_group)
) { ) {
@@ -429,20 +419,20 @@ private fun BookshelfManageScreen(
showGroupSelectSheet = true showGroupSelectSheet = true
} }
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Upload, Icons.Default.Upload,
"导出选中" "导出选中"
) { ) {
exportSelected() exportSelected()
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Delete, Icons.Default.Delete,
stringResource(R.string.clear_cache) stringResource(R.string.clear_cache)
) { ) {
viewModel.dispatch(BookshelfManageScreenIntent.ClearCachesForBooks(selectedBookUrls)) viewModel.dispatch(BookshelfManageScreenIntent.ClearCachesForBooks(selectedBookUrls))
clearSelection() clearSelection()
}, },
BookshelfManageFabAction( FabMenuItem(
Icons.Default.Delete, Icons.Default.Delete,
stringResource(R.string.delete) stringResource(R.string.delete)
) { ) {
@@ -620,44 +610,14 @@ private fun BookshelfManageScreen(
) )
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButtonMenu( AppFloatingActionButtonMenu(
modifier = Modifier.offset(x = 16.dp, y = 16.dp), modifier = Modifier.offset(x = 16.dp, y = 16.dp),
expanded = fabMenuExpanded, expanded = fabMenuExpanded,
button = { onExpandedChange = { fabMenuExpanded = it },
ToggleFloatingActionButton( items = fabItems,
modifier = Modifier visible = true,
.animateFloatingActionButton( focusRequester = focusRequester
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) }
)
}
}
} }
) { paddingValues -> ) { paddingValues ->
val renderVersion by rememberUpdatedState(state.cacheVersion) val renderVersion by rememberUpdatedState(state.cacheVersion)
@@ -681,6 +641,15 @@ private fun BookshelfManageScreen(
stringResource(R.string.cache_download_failed, it) stringResource(R.string.cache_download_failed, it)
} }
val isSelected = selectedBookUrls.contains(book.bookUrl) 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) { val exportMsg = remember(renderVersion, book.bookUrl) {
ExportBookService.exportMsg[book.bookUrl] ExportBookService.exportMsg[book.bookUrl]
} }
@@ -689,7 +658,7 @@ private fun BookshelfManageScreen(
key = book.bookUrl, key = book.bookUrl,
enabled = canReorderBooks enabled = canReorderBooks
) { ) {
NormalCard( GlassCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then( .then(
@@ -701,11 +670,7 @@ private fun BookshelfManageScreen(
), ),
onClick = { toggleBookSelection(book) }, onClick = { toggleBookSelection(book) },
onLongClick = { toggleBookSelection(book) }, onLongClick = { toggleBookSelection(book) },
containerColor = if (isSelected) { containerColor = animatedContainerColor
LegadoTheme.colorScheme.surfaceContainerHigh
} else {
LegadoTheme.colorScheme.surfaceContainerLow
}
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -36,10 +36,8 @@ 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
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted 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.BookmarkAdd
import androidx.compose.material.icons.filled.CheckCircle 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.Download
import androidx.compose.material.icons.filled.DownloadForOffline import androidx.compose.material.icons.filled.DownloadForOffline
import androidx.compose.material.icons.filled.ExpandMore 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.material.icons.rounded.LocationOn
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi 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.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -76,7 +68,6 @@ 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
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext 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.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.CollapsibleHeader import io.legado.app.ui.widget.CollapsibleHeader
import io.legado.app.ui.widget.components.ActionItem 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.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage 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.SelectionBottomBar
import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet import io.legado.app.ui.widget.components.bookmark.BookmarkEditSheet
import io.legado.app.ui.widget.components.bookmark.BookmarkItem import io.legado.app.ui.widget.components.bookmark.BookmarkItem
@@ -215,7 +208,7 @@ fun TocScreen(
val fabItems = remember(state.items) { val fabItems = remember(state.items) {
listOf( listOf(
FabAction(Icons.Default.LocationOn, "定位至当前阅读") { FabMenuItem(Icons.Default.LocationOn, "定位至当前阅读") {
scope.launch { scope.launch {
val target = state.items.indexOfFirst { it.isDur } val target = state.items.indexOfFirst { it.isDur }
if (target != -1) { if (target != -1) {
@@ -226,13 +219,13 @@ fun TocScreen(
} }
} }
}, },
FabAction(Icons.Default.VerticalAlignTop, "移至顶部") { FabMenuItem(Icons.Default.VerticalAlignTop, "移至顶部") {
scope.launch { listState.animateScrollToItem(0) } scope.launch { listState.animateScrollToItem(0) }
}, },
FabAction(Icons.Default.VerticalAlignBottom, "移至底部") { FabMenuItem(Icons.Default.VerticalAlignBottom, "移至底部") {
scope.launch { listState.animateScrollToItem(state.items.size) } scope.launch { listState.animateScrollToItem(state.items.size) }
}, },
FabAction(Icons.Default.DownloadForOffline, "下载全部") { FabMenuItem(Icons.Default.DownloadForOffline, "下载全部") {
viewModel.downloadAll() viewModel.downloadAll()
} }
) )
@@ -509,45 +502,15 @@ fun TocScreen(
) )
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButtonMenu( AppFloatingActionButtonMenu(
modifier = Modifier modifier = Modifier
.offset(x = 16.dp, y = 16.dp), .offset(x = 16.dp, y = 16.dp),
expanded = fabMenuExpanded, expanded = fabMenuExpanded,
button = { onExpandedChange = { fabMenuExpanded = it },
ToggleFloatingActionButton( items = fabItems,
modifier = Modifier visible = shouldShowFab,
.animateFloatingActionButton( focusRequester = focusRequester
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) }
)
}
}
} }
) { padding -> ) { padding ->
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
@@ -4,7 +4,7 @@ import android.app.Application
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.legado.app.base.BaseRuleViewModel import io.legado.app.base.BaseRuleViewModel
@@ -109,8 +109,6 @@ private data class TitleCacheKey(
val chapterCount: Int val chapterCount: Int
) )
data class FabAction(val icon: ImageVector, val label: String, val action: () -> Unit)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class TocViewModel( class TocViewModel(
application: Application, application: Application,
@@ -219,7 +219,9 @@ fun RssFavoritesScreen(
path = rssStar.image, path = rssStar.image,
sourceOrigin = rssStar.origin, sourceOrigin = rssStar.origin,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.size(54.dp) modifier = Modifier
.padding(vertical = 12.dp)
.size(54.dp)
) )
} }
} else null, } else null,
@@ -3,24 +3,15 @@
package io.legado.app.ui.rss.read package io.legado.app.ui.rss.read
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri
import android.net.http.SslError
import android.os.SystemClock
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.JavascriptInterface
import android.webkit.SslErrorHandler
import android.webkit.URLUtil
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material.icons.filled.Check 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.material.icons.filled.VolumeUp
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon 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.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -59,40 +45,30 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.script.rhino.runScriptWithContext
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.http.CookieManager 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.config.otherConfig.OtherConfig
import io.legado.app.ui.login.SourceLoginActivity 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.AppTextField
import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow 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.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.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu 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.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet 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.NetworkUtils
import io.legado.app.utils.isTrue
import io.legado.app.utils.keepScreenOn import io.legado.app.utils.keepScreenOn
import io.legado.app.utils.longSnackbar
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.setDarkeningAllowed import io.legado.app.utils.setDarkeningAllowed
import io.legado.app.utils.share import io.legado.app.utils.share
@@ -101,7 +77,6 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.toggleSystemBar import io.legado.app.utils.toggleSystemBar
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.net.URLDecoder
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@@ -140,7 +115,6 @@ fun RssReadRouteScreen(
val content by viewModel.contentState.collectAsStateWithLifecycle() val content by viewModel.contentState.collectAsStateWithLifecycle()
val analyzeUrl by viewModel.urlState.collectAsStateWithLifecycle() val analyzeUrl by viewModel.urlState.collectAsStateWithLifecycle()
val rssStar by viewModel.rssStarState.collectAsStateWithLifecycle()
val isSpeaking by viewModel.isSpeakingState.collectAsStateWithLifecycle() val isSpeaking by viewModel.isSpeakingState.collectAsStateWithLifecycle()
val fallbackUserAgent = OtherConfig.userAgent val fallbackUserAgent = OtherConfig.userAgent
@@ -175,19 +149,29 @@ fun RssReadRouteScreen(
val article = viewModel.rssArticle ?: return@LaunchedEffect val article = viewModel.rssArticle ?: return@LaunchedEffect
val url = NetworkUtils.getAbsoluteURL(article.origin, article.link) val url = NetworkUtils.getAbsoluteURL(article.origin, article.link)
val html = viewModel.clHtml(body) val html = viewModel.clHtml(body)
if (viewModel.rssSource?.loadWithBaseUrl == true) { if (currentWebView.url != url) {
currentWebView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url) if (viewModel.rssSource?.loadWithBaseUrl == true) {
} else { currentWebView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url)
currentWebView.loadDataWithBaseURL(null, html, "text/html;charset=utf-8", "utf-8", url) } else {
currentWebView.loadDataWithBaseURL(
null,
html,
"text/html;charset=utf-8",
"utf-8",
url
)
}
} }
} }
LaunchedEffect(analyzeUrl, webView) { LaunchedEffect(analyzeUrl, webView) {
val url = analyzeUrl ?: return@LaunchedEffect val url = analyzeUrl ?: return@LaunchedEffect
val currentWebView = webView ?: return@LaunchedEffect val currentWebView = webView ?: return@LaunchedEffect
CookieManager.applyToWebView(url.url) if (currentWebView.url != url.url) {
currentWebView.settings.userAgentString = url.getUserAgent() CookieManager.applyToWebView(url.url)
currentWebView.loadUrl(url.url, url.headerMap) currentWebView.settings.userAgentString = url.getUserAgent()
currentWebView.loadUrl(url.url, url.headerMap)
}
} }
BackHandler { 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()) { Box(modifier = Modifier.fillMaxSize()) {
Scaffold( AppScaffold(
disableHazeSource = true,
topBar = { topBar = {
TopAppBar( GlassTopAppBar(
title = { title = pageTitle.ifBlank { defaultTopBarTitle },
Text(
text = pageTitle.ifBlank { defaultTopBarTitle },
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = { navigationIcon = {
TopBarNavigationButton(onClick = onBackClick) TopBarNavigationButton(onClick = onBackClick)
}, },
@@ -236,11 +222,11 @@ fun RssReadRouteScreen(
} }
) )
Box { Box {
TopBarActionButton( TopBarActionButton(
imageVector = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
contentDescription = "Menu", contentDescription = "Menu",
onClick = { showMenu = true } onClick = { showMenu = true }
) )
RoundDropdownMenu( RoundDropdownMenu(
expanded = showMenu, expanded = showMenu,
onDismissRequest = { showMenu = false } onDismissRequest = { showMenu = false }
@@ -256,7 +242,7 @@ fun RssReadRouteScreen(
context.share(it.link) context.share(it.link)
} ?: context.toastOnUi(R.string.null_url) } ?: context.toastOnUi(R.string.null_url)
} }
) )
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = if (isSpeaking) stringResource(R.string.aloud_stop) else stringResource( text = if (isSpeaking) stringResource(R.string.aloud_stop) else stringResource(
R.string.read_aloud R.string.read_aloud
@@ -276,21 +262,21 @@ fun RssReadRouteScreen(
Jsoup.parse(html).text() Jsoup.parse(html).text()
) )
} }
} }
} }
) )
if (!viewModel.rssSource?.loginUrl.isNullOrBlank()) { if (!viewModel.rssSource?.loginUrl.isNullOrBlank()) {
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = stringResource(R.string.login), text = stringResource(R.string.login),
leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) }, leadingIcon = { MenuItemIcon(Icons.AutoMirrored.Filled.Login) },
onClick = { onClick = {
dismiss() dismiss()
context.startActivity<SourceLoginActivity> { context.startActivity<SourceLoginActivity> {
putExtra("type", "rssSource") putExtra("type", "rssSource")
putExtra("key", viewModel.rssSource?.sourceUrl) putExtra("key", viewModel.rssSource?.sourceUrl)
}
} }
) }
)
} }
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = stringResource(R.string.redirect_policy), text = stringResource(R.string.redirect_policy),
@@ -301,8 +287,9 @@ fun RssReadRouteScreen(
leadingIcon = { MenuItemIcon(Icons.Default.OpenInBrowser) }, leadingIcon = { MenuItemIcon(Icons.Default.OpenInBrowser) },
onClick = { onClick = {
dismiss() dismiss()
webView?.url?.let { context.openUrl(it) } ?: context.toastOnUi("url null") webView?.url?.let { context.openUrl(it) }
} ?: context.toastOnUi("url null")
}
) )
} }
RoundDropdownMenu( RoundDropdownMenu(
@@ -310,27 +297,30 @@ fun RssReadRouteScreen(
onDismissRequest = { showRedirectMenu = false } onDismissRequest = { showRedirectMenu = false }
) { dismiss -> ) { dismiss ->
RedirectPolicy.entries.forEach { policy -> RedirectPolicy.entries.forEach { policy ->
RoundDropdownMenuItem( RoundDropdownMenuItem(
text = policy.title(), text = policy.title(),
onClick = { onClick = {
dismiss() dismiss()
showMenu = false showMenu = false
viewModel.rssSource?.let { source -> viewModel.rssSource?.let { source ->
viewModel.updateRssSourceRedirectPolicy(source.sourceUrl, policy.name) viewModel.updateRssSourceRedirectPolicy(
redirectPolicy = policy source.sourceUrl,
} policy.name
context.toastOnUi("重定向策略已更新") )
}, redirectPolicy = policy
trailingIcon = {
if (policy == redirectPolicy) {
Icon(Icons.Default.Check, null)
}
} }
) context.toastOnUi("重定向策略已更新")
} },
trailingIcon = {
if (policy == redirectPolicy) {
Icon(Icons.Default.Check, null)
}
}
)
} }
} }
} }
}
) )
} }
) { paddingValues -> ) { paddingValues ->
@@ -369,18 +359,18 @@ fun RssReadRouteScreen(
} }
}, },
onHideCustomView = { hideCustomView() } onHideCustomView = { hideCustomView() }
)
) )
} )
}
) )
if (webProgress in 0..99) { if (webProgress in 0..99) {
AppLinearProgressIndicator( AppLinearProgressIndicator(
progress = webProgress / 100f, progress = webProgress / 100f,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
}
} }
} }
}
customView?.let { view -> customView?.let { view ->
key(view) { key(view) {
@@ -29,6 +29,7 @@ class VisibleWebView(
private var lastSelectedText: String = "" private var lastSelectedText: String = ""
init { init {
setBackgroundColor(0)
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.domStorageEnabled = true settings.domStorageEnabled = true
@@ -1,23 +1,55 @@
package io.legado.app.ui.widget.components 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.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonMenu
import androidx.compose.material3.FloatingActionButtonMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip 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.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.material3.rememberTooltipState import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector 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.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver 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 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.FloatingActionButton as MiuixFloatingActionButton
import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
import top.yukonga.miuix.kmp.basic.Text as MiuixText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @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<FabMenuItem>,
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) }
)
}
}
}
}
@@ -17,7 +17,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
@@ -44,6 +43,7 @@ fun AppScaffold(
contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface), contentColor: Color = contentColorFor(MiuixTheme.colorScheme.surface),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
alwaysDrawBehindBars: Boolean = false, alwaysDrawBehindBars: Boolean = false,
disableHazeSource: Boolean = false,
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
val isDark = isSystemInDarkTheme() val isDark = isSystemInDarkTheme()
@@ -97,7 +97,10 @@ fun AppScaffold(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.responsiveHazeSource(hazeState) .then(
if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState)
else Modifier
)
.then( .then(
if (contentDrawsBehindBars) Modifier if (contentDrawsBehindBars) Modifier
else Modifier.padding(scaffoldPadding) else Modifier.padding(scaffoldPadding)
@@ -136,7 +139,10 @@ fun AppScaffold(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.responsiveHazeSource(hazeState) .then(
if (!disableHazeSource) Modifier.responsiveHazeSource(hazeState)
else Modifier
)
.then( .then(
if (contentDrawsBehindBars) Modifier if (contentDrawsBehindBars) Modifier
else Modifier.padding(scaffoldPadding) else Modifier.padding(scaffoldPadding)
@@ -22,9 +22,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import io.legado.app.ui.theme.adaptiveHorizontalPadding import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.SearchBar 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.icon.AppIcons
import io.legado.app.ui.widget.components.list.ListUiState import io.legado.app.ui.widget.components.list.ListUiState
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
@@ -118,12 +115,6 @@ fun <T> DynamicTopAppBar(
onQueryChange = onSearchQueryChange, onQueryChange = onSearchQueryChange,
onSearch = onSearchSubmit, onSearch = onSearchSubmit,
placeholder = searchPlaceholder, placeholder = searchPlaceholder,
leadingIcon = {
AppIcon(
imageVector = searchLeadingIcon,
contentDescription = null
)
},
trailingIcon = searchTrailingIcon, trailingIcon = searchTrailingIcon,
dropdownMenu = searchDropdownMenu dropdownMenu = searchDropdownMenu
) )
@@ -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
)
)
}
}
}
@@ -3,9 +3,9 @@
package io.legado.app.utils package io.legado.app.utils
import android.content.res.Configuration 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 val Configuration.isNightMode: Boolean
get() { get() {