Просмотр исходного кода

实现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>
Sun-QAQ 1 год назад
Родитель
Сommit
f25e885b48

+ 7 - 0
app/src/main/AndroidManifest.xml

@@ -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" />

+ 18 - 0
app/src/main/java/io/legado/app/help/JsExtensions.kt

@@ -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("启动跳转确认失败")
+        }
+    }
 }
 }

+ 63 - 0
app/src/main/java/io/legado/app/ui/javascript/ConfirmationDialogActivity.kt

@@ -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()
+    }
+}

+ 56 - 0
app/src/main/res/layout/confirmation_dialog.xml

@@ -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 - 0
app/src/main/res/values/strings.xml

@@ -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 - 0
app/src/main/res/values/styles.xml

@@ -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>