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 {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".WebViewActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 此时不再需要权限检查方法
|
||||
}
|
||||
|
|
|
|||
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]
|
||||
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" }
|
||||
|
|
|
|||
Loading…
Reference in a new issue