From e0bd5602aba92a5b624a5fc0acdf146d126c48b7 Mon Sep 17 00:00:00 2001 From: moreoronce Date: Sat, 5 Oct 2024 19:57:47 +0800 Subject: [PATCH] feat: Add URL validation and prompt feature - Implemented regular expression validation for the input URL in WebViewActivity. - Display a prompt saying "Please enter a valid website address" if the URL format is incorrect. - Maintained existing functionality to ensure that only valid URLs can be loaded. - Split the WebView functionality from MainActivity into a separate page --- app/build.gradle.kts | 5 +- app/src/main/AndroidManifest.xml | 1 + .../java/com/example/lobchat/MainActivity.kt | 78 ++++---------- .../com/example/lobchat/WebViewActivity.kt | 101 ++++++++++++++++++ app/src/main/res/layout/activity_web_view.xml | 15 +++ gradle/libs.versions.toml | 4 + 6 files changed, 143 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/example/lobchat/WebViewActivity.kt create mode 100644 app/src/main/res/layout/activity_web_view.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b26b192..b750540 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,7 +10,7 @@ android { defaultConfig { applicationId = "com.example.lobchat" minSdk = 30 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" @@ -64,7 +64,8 @@ dependencies { implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.androidx.appcompat) - implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation(libs.androidx.constraintlayout) + implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fa344e..7ae3f0f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/app/src/main/java/com/example/lobchat/MainActivity.kt b/app/src/main/java/com/example/lobchat/MainActivity.kt index 167f44d..701e0a6 100644 --- a/app/src/main/java/com/example/lobchat/MainActivity.kt +++ b/app/src/main/java/com/example/lobchat/MainActivity.kt @@ -12,8 +12,6 @@ import androidx.appcompat.app.AppCompatActivity import android.widget.Toast import android.widget.EditText import android.widget.Button -import android.view.View -import android.util.Log class MainActivity : AppCompatActivity() { @@ -67,77 +65,39 @@ class MainActivity : AppCompatActivity() { } } - // 确保链接在 WebView 内部打开,而不是在默认浏览器中打开 - webView.webViewClient = object : WebViewClient() { - override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { - Toast.makeText(this@MainActivity, "Network error, please try again later.", Toast.LENGTH_SHORT).show() - } - - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - // 注入 JavaScript 来禁用 PWA 安装提示 - view?.evaluateJavascript( - """ - window.addEventListener('beforeinstallprompt', function(e) { - e.preventDefault(); - console.log('Install prompt blocked by WebView'); - }); - """.trimIndent(), null - ) - // 隐藏输入框和按钮 - urlInput.visibility = View.GONE - loadUrlButton.visibility = View.GONE - webView.visibility = View.VISIBLE - } - } - - // 设置 WebChromeClient 以处理文件选择和 JavaScript 错误 - webView.webChromeClient = object : WebChromeClient() { - override fun onShowFileChooser( - webView: WebView?, - filePathCallback: ValueCallback>, - fileChooserParams: FileChooserParams? - ): Boolean { - this@MainActivity.filePathCallback?.onReceiveValue(null) - this@MainActivity.filePathCallback = filePathCallback - - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - - // 使用新的 ActivityResultLauncher 启动文件选择器 - fileChooserLauncher.launch(Intent.createChooser(intent, "Select Picture")) - return true - } - - override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { - Log.e("WebViewConsole", "JavaScript Error: ${consoleMessage?.message()} -- From line ${consoleMessage?.lineNumber()} of ${consoleMessage?.sourceId()}") - return true - } - } - // 点击按钮后加载用户输入的 URL loadUrlButton.setOnClickListener { - var url = urlInput.text.toString().trim() + val url = urlInput.text.toString().trim() + val urlPattern = Regex("^(?:(http|https):\\/\\/)?((?:[\\w-]+\\.)+[a-z0-9]+)((?:\\/[^/?#]*)+)?(\\?[^#]+)?(#.+)?$") + if (url.isNotEmpty()) { // 确保 URL 包含 http 或 https - if (!url.startsWith("http://") && !url.startsWith("https://")) { - url = "https://$url" + val formattedUrl = if (!urlPattern.matches(url)) { + // 如果 URL 格式不正确,则提示用户 + Toast.makeText(this, "请输入正确的网站地址", Toast.LENGTH_SHORT).show() + return@setOnClickListener // 结束函数 + } else { + // 确保 URL 包含 http 或 https + if (!url.startsWith("http://") && !url.startsWith("https://")) { + "https://$url" + } else { + url + } } // 保存用户输入的 URL 到 SharedPreferences with(sharedPreferences.edit()) { - putString(KEY_SAVED_URL, url) + putString(KEY_SAVED_URL, formattedUrl) apply() } - // 加载用户输入的 URL - webView.loadUrl(url) + // 启动 WebViewActivity + val intent = Intent(this, WebViewActivity::class.java) + intent.putExtra("URL", formattedUrl) + startActivity(intent) } else { Toast.makeText(this, "Please enter a valid URL", Toast.LENGTH_SHORT).show() } } } - - // 此时不再需要权限检查方法 } diff --git a/app/src/main/java/com/example/lobchat/WebViewActivity.kt b/app/src/main/java/com/example/lobchat/WebViewActivity.kt new file mode 100644 index 0000000..6bdec34 --- /dev/null +++ b/app/src/main/java/com/example/lobchat/WebViewActivity.kt @@ -0,0 +1,101 @@ +package com.example.lobchat + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.webkit.* +import androidx.appcompat.app.AppCompatActivity +import androidx.activity.result.ActivityResultLauncher +import com.google.android.material.snackbar.Snackbar + +class WebViewActivity : AppCompatActivity() { + + private lateinit var webView: WebView + private var filePathCallback: ValueCallback>? = null + private lateinit var fileChooserLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_web_view) // 确保这个布局文件存在 + + // 初始化 WebView + webView = findViewById(R.id.webView) + setupWebView() + + // 获取传递的 URL + val url = intent.getStringExtra("URL") // 确保这个 key 和 MainActivity 一致 + if (!url.isNullOrEmpty()) { + webView.loadUrl(url) + } else { + showSnackbar("No URL provided") + } + } + + private fun setupWebView() { + val webSettings: WebSettings = webView.settings + webSettings.javaScriptEnabled = true + webSettings.domStorageEnabled = true + + webView.webViewClient = object : WebViewClient() { + override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { + showSnackbar("Network error, please try again later.") + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + // 注入 JavaScript 来禁用 PWA 安装提示 + injectJavaScript(view) + } + } + + webView.webChromeClient = object : WebChromeClient() { + override fun onShowFileChooser( + webView: WebView?, + filePathCallback: ValueCallback>, + fileChooserParams: FileChooserParams? + ): Boolean { + this@WebViewActivity.filePathCallback?.onReceiveValue(null) + this@WebViewActivity.filePathCallback = filePathCallback + launchFileChooser() + return true + } + + override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { + Log.e("WebViewConsole", "JavaScript Error: ${consoleMessage?.message()} -- From line ${consoleMessage?.lineNumber()} of ${consoleMessage?.sourceId()}") + return true + } + } + } + + private fun injectJavaScript(view: WebView?) { + view?.evaluateJavascript( + """ + window.addEventListener('beforeinstallprompt', function(e) { + e.preventDefault(); + console.log('Install prompt blocked by WebView'); + }); + """.trimIndent(), null + ) + } + + private fun launchFileChooser() { + val intent = Intent(Intent.ACTION_GET_CONTENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "image/*" + } + fileChooserLauncher.launch(Intent.createChooser(intent, "Select Picture")) + } + + private fun showSnackbar(message: String) { + Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG).show() + } + + override fun onBackPressed() { + if (webView.canGoBack()) { + webView.goBack() + } else { + super.onBackPressed() + } + } +} diff --git a/app/src/main/res/layout/activity_web_view.xml b/app/src/main/res/layout/activity_web_view.xml new file mode 100644 index 0000000..d5e5890 --- /dev/null +++ b/app/src/main/res/layout/activity_web_view.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 34048d0..da39d16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.6.0" +constraintlayout = "2.1.4" kotlin = "1.9.0" coreKtx = "1.10.1" junit = "4.13.2" @@ -9,9 +10,11 @@ lifecycleRuntimeKtx = "2.6.1" activityCompose = "1.8.0" composeBom = "2024.04.01" appcompat = "1.7.0" +material = "1.12.0" [libraries] +androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } @@ -27,6 +30,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }