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
This commit is contained in:
moreoronce 2024-10-05 19:57:47 +08:00
parent c4f50c2c14
commit e0bd5602ab
6 changed files with 143 additions and 61 deletions

View file

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

View file

@ -19,6 +19,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".WebViewActivity" />
</application>
</manifest>

View file

@ -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<Array<Uri>>,
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()
}
}
}
// 此时不再需要权限检查方法
}

View file

@ -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<Array<Uri>>? = null
private lateinit var fileChooserLauncher: ActivityResultLauncher<Intent>
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<Array<Uri>>,
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()
}
}
}

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WebViewActivity">
<!-- WebView 控件 -->
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

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