feat: Update storage permission handling for image access

- Implemented request for READ_EXTERNAL_STORAGE permission to allow users to select images.
- Optimized permission request logic to avoid unnecessary prompts if permission has already been granted.
- Added user-friendly dialogs to guide users to app settings when permissions are denied.
- Enhanced error handling and logging for WebView to improve debugging and user experience.
- Loaded last entered URL from SharedPreferences for better usability.
- Updated UI interactions to hide input elements when loading web content.
- Remove unnecessary permission granted.

This update improves the overall user experience by streamlining the permission process and enhancing image selection functionality.
This commit is contained in:
moreoronce 2024-10-05 19:06:53 +08:00
parent 1e2bcee52c
commit c4f50c2c14
3 changed files with 34 additions and 93 deletions

View file

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="Unnamed"> <SelectionState runConfigName="Unnamed">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-10-05T10:05:39.736702200Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Archol\.android\avd\Pixel_5_API_28.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View file

@ -2,7 +2,6 @@
<!-- 允许应用访问互联网 --> <!-- 允许应用访问互联网 -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<!-- 添加 namespace --> <!-- 添加 namespace -->
<application <application

View file

@ -1,48 +1,48 @@
package com.example.lobchat package com.example.lobchat
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.webkit.* import android.webkit.*
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
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.view.View
import androidx.appcompat.app.AlertDialog
import android.util.Log import android.util.Log
import android.provider.Settings
import androidx.constraintlayout.widget.ConstraintLayout
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView private lateinit var webView: WebView
private lateinit var urlInputLayout: ConstraintLayout
private lateinit var urlInput: EditText
private lateinit var loadUrlButton: Button
private var filePathCallback: ValueCallback<Array<Uri>>? = null private var filePathCallback: ValueCallback<Array<Uri>>? = null
private lateinit var fileChooserLauncher: ActivityResultLauncher<Intent> private lateinit var fileChooserLauncher: ActivityResultLauncher<Intent>
private lateinit var urlInput: EditText
private lateinit var loadUrlButton: Button
companion object {
const val SHARED_PREFS_NAME = "lobchat_prefs"
const val KEY_SAVED_URL = "saved_url"
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
// 初始化视图组件 // 初始化视图组件
webView = findViewById(R.id.webView) webView = findViewById(R.id.webView)
urlInputLayout = findViewById(R.id.urlInputLayout)
urlInput = findViewById(R.id.urlInput) urlInput = findViewById(R.id.urlInput)
loadUrlButton = findViewById(R.id.loadUrlButton) loadUrlButton = findViewById(R.id.loadUrlButton)
// 检查并请求存储权限 // 从 SharedPreferences 加载上次保存的 URL
if (!hasStoragePermission()) { val sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
requestStoragePermission() val savedUrl = sharedPreferences.getString(KEY_SAVED_URL, "")
if (!savedUrl.isNullOrEmpty()) {
urlInput.setText(savedUrl)
} }
// 设置状态栏为透明,使系统自动使用默认的颜色 // 设置状态栏为透明,使系统自动使用默认的颜色
@ -84,6 +84,10 @@ class MainActivity : AppCompatActivity() {
}); });
""".trimIndent(), null """.trimIndent(), null
) )
// 隐藏输入框和按钮
urlInput.visibility = View.GONE
loadUrlButton.visibility = View.GONE
webView.visibility = View.VISIBLE
} }
} }
@ -121,89 +125,19 @@ class MainActivity : AppCompatActivity() {
url = "https://$url" url = "https://$url"
} }
// 保存用户输入的 URL 到 SharedPreferences
with(sharedPreferences.edit()) {
putString(KEY_SAVED_URL, url)
apply()
}
// 加载用户输入的 URL // 加载用户输入的 URL
webView.loadUrl(url) webView.loadUrl(url)
// 切换视图:隐藏 URL 输入部分并显示 WebView
urlInputLayout.visibility = View.GONE
webView.visibility = View.VISIBLE
} 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()
} }
} }
} }
// 方法:检查是否具有存储权限 // 此时不再需要权限检查方法
private fun hasStoragePermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_MEDIA_IMAGES
) == PackageManager.PERMISSION_GRANTED
} else {
ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
}
// 方法:请求存储权限
private fun requestStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_MEDIA_IMAGES)) {
showPermissionRationale(android.Manifest.permission.READ_MEDIA_IMAGES)
} else {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_MEDIA_IMAGES), 1)
}
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
showPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE)
} else {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE), 1)
}
}
}
// 方法:显示权限请求解释
private fun showPermissionRationale(permission: String) {
AlertDialog.Builder(this)
.setTitle("Permission Required")
.setMessage("This app requires access to your storage to select images. Please grant the permission.")
.setPositiveButton("OK") { _, _ ->
ActivityCompat.requestPermissions(this, arrayOf(permission), 1)
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.create()
.show()
}
// 处理权限请求结果
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
Toast.makeText(this, "Read permission granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Read permission denied", Toast.LENGTH_SHORT).show()
// 引导用户前往设置手动开启权限
AlertDialog.Builder(this)
.setTitle("Permission Denied")
.setMessage("Storage permission is required for this app. Please allow it from settings.")
.setPositiveButton("Settings") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.create()
.show()
}
}
}
} }