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:
parent
c4f50c2c14
commit
e0bd5602ab
6 changed files with 143 additions and 61 deletions
|
|
@ -10,7 +10,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.example.lobchat"
|
applicationId = "com.example.lobchat"
|
||||||
minSdk = 30
|
minSdk = 30
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
|
|
@ -64,7 +64,8 @@ dependencies {
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
implementation(libs.material)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".WebViewActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.view.View
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
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
|
// 点击按钮后加载用户输入的 URL
|
||||||
loadUrlButton.setOnClickListener {
|
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()) {
|
if (url.isNotEmpty()) {
|
||||||
// 确保 URL 包含 http 或 https
|
// 确保 URL 包含 http 或 https
|
||||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
val formattedUrl = if (!urlPattern.matches(url)) {
|
||||||
url = "https://$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
|
// 保存用户输入的 URL 到 SharedPreferences
|
||||||
with(sharedPreferences.edit()) {
|
with(sharedPreferences.edit()) {
|
||||||
putString(KEY_SAVED_URL, url)
|
putString(KEY_SAVED_URL, formattedUrl)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载用户输入的 URL
|
// 启动 WebViewActivity
|
||||||
webView.loadUrl(url)
|
val intent = Intent(this, WebViewActivity::class.java)
|
||||||
|
intent.putExtra("URL", formattedUrl)
|
||||||
|
startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Please enter a valid URL", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Please enter a valid URL", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 此时不再需要权限检查方法
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
app/src/main/java/com/example/lobchat/WebViewActivity.kt
Normal file
101
app/src/main/java/com/example/lobchat/WebViewActivity.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/src/main/res/layout/activity_web_view.xml
Normal file
15
app/src/main/res/layout/activity_web_view.xml
Normal 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>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.6.0"
|
agp = "8.6.0"
|
||||||
|
constraintlayout = "2.1.4"
|
||||||
kotlin = "1.9.0"
|
kotlin = "1.9.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|
@ -9,9 +10,11 @@ lifecycleRuntimeKtx = "2.6.1"
|
||||||
activityCompose = "1.8.0"
|
activityCompose = "1.8.0"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
|
material = "1.12.0"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
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-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue