实现JS方法(openUrl),能够弹窗提示用户是否跳转外部链接 (#4649)

* feat(ui): 添加外部链接跳转确认对话框

- 新增 ConfirmationDialogActivity 用于显示跳转确认对话框
- 在 AndroidManifest.xml 中注册新活动
- 添加 confirmation_dialog.xml 布局文件- 在 JsExtensions.kt 中实现 openUrl 函数以启动对话框
- 更新 styles.xml 添加透明主题样式
- 在 strings.xml 中添加新的错误提示字符串

* feat(dialog): 优化外部链接/应用跳转确认对话框

- 更新对话框布局,增加文本大小设置
- 添加来源标签显示,提高用户识别度
- 优化对话框逻辑,正确处理视图初始化顺序
- 更新字符串资源,支持来源标签显示

---------

Co-authored-by: SunQAQ <sunqi87c6@gmail.com>
This commit is contained in:
Sun-QAQ
2025-02-11 19:49:08 +08:00
committed by GitHub
co-authored by SunQAQ
parent 7bad3e3967
commit f25e885b48
6 changed files with 158 additions and 0 deletions
+7
View File
@@ -433,6 +433,13 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.javascript.ConfirmationDialogActivity"
android:theme="@style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleInstance"/>
<service <service
android:name=".service.CheckSourceService" android:name=".service.CheckSourceService"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
@@ -2,6 +2,8 @@ package io.legado.app.help
import android.net.Uri import android.net.Uri
import android.webkit.WebSettings import android.webkit.WebSettings
import android.app.AlertDialog
import android.content.Intent
import androidx.annotation.Keep import androidx.annotation.Keep
import cn.hutool.core.codec.Base64 import cn.hutool.core.codec.Base64
import cn.hutool.core.util.HexUtil import cn.hutool.core.util.HexUtil
@@ -21,6 +23,7 @@ import io.legado.app.help.source.SourceVerificationHelp
import io.legado.app.model.Debug import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.QueryTTF import io.legado.app.model.analyzeRule.QueryTTF
import io.legado.app.ui.javascript.ConfirmationDialogActivity
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.ChineseUtils import io.legado.app.utils.ChineseUtils
import io.legado.app.utils.EncoderUtils import io.legado.app.utils.EncoderUtils
@@ -45,9 +48,11 @@ import io.legado.app.utils.readText
import io.legado.app.utils.stackTraceStr import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toStringArray import io.legado.app.utils.toStringArray
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okio.use import okio.use
import org.jsoup.Connection import org.jsoup.Connection
import org.jsoup.Jsoup import org.jsoup.Jsoup
@@ -958,4 +963,17 @@ interface JsExtensions : JsEncodeUtils {
return AppConst.androidId return AppConst.androidId
} }
fun openUrl(url: String) {
try {
val intent = Intent(appCtx, ConfirmationDialogActivity::class.java).apply {
data = Uri.parse(url)
putExtra("sourceTag", getSource()?.getTag() ?: "") // 添加来源标签参数
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
appCtx.startActivity(intent)
} catch (e: Exception) {
AppLog.put("启动跳转对话框失败", e)
appCtx.toastOnUi("启动跳转确认失败")
}
}
} }
@@ -0,0 +1,63 @@
package io.legado.app.ui.javascript
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.appcompat.app.AppCompatActivity
import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.ui.widget.text.AccentTextView
import io.legado.app.utils.toastOnUi
class ConfirmationDialogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 必须先设置布局再获取控件
setContentView(R.layout.confirmation_dialog)
// 正确顺序:先设置布局再获取视图
val messageView = findViewById<TextView>(R.id.message)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
val btnNegative = findViewById<AccentTextView>(R.id.btn_negative)
val btnPositive = findViewById<AccentTextView>(R.id.btn_positive)
val url = intent?.dataString
if (url.isNullOrBlank()) {
finish()
return
}
// 处理来源标签显示
val sourceTag = intent.getStringExtra("sourceTag").takeIf { !it.isNullOrBlank() } ?: "当前来源"
messageView.text = "$sourceTag 正在请求跳转外部链接/应用,是否跳转?"
// 设置其他组件
toolbar.setNavigationOnClickListener { finish() }
btnNegative.setOnClickListener { finish() }
btnPositive.setOnClickListener {
try {
startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
} catch (e: ActivityNotFoundException) {
toastOnUi(R.string.can_not_open)
} catch (e: Exception) {
AppLog.put("打开链接失败", e)
toastOnUi(R.string.error_occurred)
}
finish()
}
}
override fun onBackPressed() {
super.onBackPressed()
finish()
}
}
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_card_view"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="跳转确认"
app:titleTextAppearance="@style/ToolbarTitle" />
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="16sp"
android:textAppearance="?attr/textAppearanceBodyMedium"/>
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
app:flexDirection="row"
app:justifyContent="flex_end">
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/btn_negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/cancel"/>
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/btn_positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/ok"/>
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>
</FrameLayout>
+2
View File
@@ -1185,4 +1185,6 @@
<string name="theme_config">主题配置</string> <string name="theme_config">主题配置</string>
<string name="menu_download_after">Download the chapter after</string> <string name="menu_download_after">Download the chapter after</string>
<string name="menu_download_all">Download all chapter</string> <string name="menu_download_all">Download all chapter</string>
<string name="error_occurred" translatable="false">打开失败</string>
<string name="confirmation_message" translatable="false">%s正在请求跳转外部链接/应用,是否跳转?</string>
</resources> </resources>
+12
View File
@@ -127,4 +127,16 @@
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
</style> </style>
<!-- 基础透明主题 -->
<style name="Theme.Translucent" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
<!-- 无标题栏变体 -->
<style name="Theme.Translucent.NoTitleBar" parent="Theme.Translucent">
<item name="windowNoTitle">true</item>
</style>
</resources> </resources>