[优化] 订阅界面

This commit is contained in:
HapeLee
2026-04-26 15:13:51 +08:00
parent 7e27686fbf
commit e59a6b8ec0
2 changed files with 66 additions and 130 deletions
@@ -6,7 +6,6 @@ import android.annotation.SuppressLint
import android.net.Uri
import android.net.http.SslError
import android.os.SystemClock
import android.view.MotionEvent
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.SslErrorHandler
@@ -19,20 +18,14 @@ import android.webkit.WebViewClient
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AlertDialog
import androidx.compose.animation.animateContentSize
import androidx.appcompat.app.AppCompatActivity
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.CleaningServices
@@ -44,17 +37,18 @@ import androidx.compose.material.icons.filled.Star
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 androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -74,20 +68,16 @@ 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.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField
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.card.NormalCard
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.button.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.text.AppText
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.isTrue
import io.legado.app.utils.keepScreenOn
@@ -97,9 +87,6 @@ import io.legado.app.utils.setDarkeningAllowed
import io.legado.app.utils.share
import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.apache.commons.text.StringEscapeUtils
import org.jsoup.Jsoup
import java.net.URLDecoder
@@ -118,8 +105,7 @@ fun RssReadRouteScreen(
) {
val context = LocalContext.current
val activity = LocalActivity.current
val appCompatActivity = activity as? androidx.appcompat.app.AppCompatActivity
val scope = rememberCoroutineScope()
val appCompatActivity = activity as? AppCompatActivity
val defaultTopBarTitle = stringResource(R.string.rss)
var pageTitle by remember(title, defaultTopBarTitle) {
@@ -128,9 +114,6 @@ fun RssReadRouteScreen(
var webProgress by remember { mutableIntStateOf(100) }
var showMenu by remember { mutableStateOf(false) }
var showRedirectMenu by remember { mutableStateOf(false) }
var showFloatingCard by remember { mutableStateOf(true) }
var showFloatingCardJob by remember { mutableStateOf<Job?>(null) }
var webView by remember { mutableStateOf<WebView?>(null) }
var redirectPolicy by remember { mutableStateOf(RedirectPolicy.ALLOW_ALL) }
@@ -189,102 +172,26 @@ fun RssReadRouteScreen(
}
}
Box(modifier = Modifier.fillMaxSize()) {
VisibleWebViewCompose(
modifier = Modifier.fillMaxSize(),
onCreated = { createdWebView ->
webView = createdWebView
configureRssReadWebView(
webView = createdWebView,
context = context,
activity = activity,
appCompatActivity = appCompatActivity,
viewModel = viewModel,
initialTitle = title,
redirectPolicyProvider = { redirectPolicy },
callbacks = RssReadWebControllerCallbacks(
onProgressChanged = { webProgress = it },
onPageTitleResolved = { resolved ->
pageTitle = resolved.ifBlank { defaultTopBarTitle }
}
)
)
createdWebView.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
showFloatingCardJob?.cancel()
showFloatingCard = false
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
createdWebView.performClick()
showFloatingCardJob?.cancel()
showFloatingCardJob = scope.launch {
delay(180L)
showFloatingCard = true
}
}
}
false
}
}
)
if (webProgress in 0..99) {
LinearProgressIndicator(
progress = { webProgress / 100f },
modifier = Modifier.fillMaxWidth()
)
}
TopFloatingStickyItem(
item = if (showFloatingCard) {
RssReadFloatingSummary(
titleText = pageTitle.ifBlank { defaultTopBarTitle },
progressText = if (webProgress in 0..99) " · ${webProgress}%" else null
)
} else {
null
},
modifier = Modifier
.align(Alignment.TopCenter)
.statusBarsPadding()
.padding(top = 6.dp),
) { summary ->
NormalCard(
modifier = Modifier.animateContentSize(),
cornerRadius = 32.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
SmallTonalIconButton(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back),
onClick = onBackClick
)
Spacer(modifier = Modifier.width(8.dp))
AppText(
text = summary.titleText,
style = LegadoTheme.typography.labelSmallEmphasized,
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = pageTitle.ifBlank { defaultTopBarTitle },
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 12.dp)
overflow = TextOverflow.Ellipsis
)
summary.progressText?.let { text ->
AppText(text = text, style = LegadoTheme.typography.labelMediumEmphasized)
Spacer(modifier = Modifier.width(6.dp))
}
SmallTonalIconButton(
},
navigationIcon = {
TopBarNavigationButton(onClick = onBackClick)
},
actions = {
TopBarActionButton(
imageVector = Icons.Default.Refresh,
contentDescription = stringResource(R.string.refresh),
onClick = { viewModel.refresh { webView?.reload() } }
)
Spacer(modifier = Modifier.width(8.dp))
SmallTonalIconButton(
TopBarActionButton(
imageVector = Icons.Default.Star,
contentDescription = stringResource(R.string.favorite),
onClick = {
@@ -294,9 +201,8 @@ fun RssReadRouteScreen(
showFavoriteSheet = true
}
)
Spacer(modifier = Modifier.width(8.dp))
Box {
SmallTonalIconButton(
TopBarActionButton(
imageVector = Icons.Default.MoreVert,
contentDescription = "Menu",
onClick = { showMenu = true }
@@ -391,6 +297,40 @@ fun RssReadRouteScreen(
}
}
}
)
}
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
VisibleWebViewCompose(
modifier = Modifier.fillMaxSize(),
onCreated = { createdWebView ->
webView = createdWebView
configureRssReadWebView(
webView = createdWebView,
context = context,
activity = activity,
appCompatActivity = appCompatActivity,
viewModel = viewModel,
initialTitle = title,
redirectPolicyProvider = { redirectPolicy },
callbacks = RssReadWebControllerCallbacks(
onProgressChanged = { webProgress = it },
onPageTitleResolved = { resolved ->
pageTitle = resolved.ifBlank { defaultTopBarTitle }
}
)
)
}
)
if (webProgress in 0..99) {
LinearProgressIndicator(
progress = { webProgress / 100f },
modifier = Modifier.fillMaxWidth()
)
}
}
}
@@ -413,11 +353,6 @@ fun RssReadRouteScreen(
)
}
private data class RssReadFloatingSummary(
val titleText: String,
val progressText: String?,
)
@Composable
private fun FavoriteEditSheet(
show: Boolean,
@@ -11,6 +11,7 @@ import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import io.legado.app.R
@@ -47,10 +48,6 @@ class VisibleWebView(
evaluateJavascript(js, null)
}
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(VISIBLE)
}
override fun performClick(): Boolean {
return super.performClick()
}
@@ -143,24 +140,28 @@ fun VisibleWebViewCompose(
onCreated: (VisibleWebView) -> Unit,
onDestroyed: (() -> Unit)? = null
) {
var webViewRef: VisibleWebView? = null
val webViewHolder = remember { WebViewHolder() }
AndroidView(
modifier = modifier,
factory = { context ->
VisibleWebView(context).also {
webViewRef = it
webViewHolder.webView = it
onCreated(it)
}
},
update = {
webViewRef = it
webViewHolder.webView = it
}
)
DisposableEffect(Unit) {
onDispose {
onDestroyed?.invoke()
webViewRef?.destroy()
webViewRef = null
webViewHolder.webView?.destroy()
webViewHolder.webView = null
}
}
}
private class WebViewHolder {
var webView: VisibleWebView? = null
}