Refactor QR code and file chooser handling in activities

This commit is contained in:
2dust
2026-01-28 19:29:31 +08:00
parent 41fd237a87
commit 3c90ee0206
8 changed files with 82 additions and 59 deletions
@@ -0,0 +1,38 @@
package com.v2ray.ang.helper
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.v2ray.ang.ui.ScannerActivity
/**
* Helper for scanning QR codes.
*
* This class encapsulates the logic for launching the QR code scanner activity
* and handling the scan result.
*/
class QRCodeScannerHelper(private val activity: AppCompatActivity) {
private var scanCallback: ((String?) -> Unit)? = null
private val scanLauncher: ActivityResultLauncher<Intent> =
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == AppCompatActivity.RESULT_OK) {
val scanResult = result.data?.getStringExtra("SCAN_RESULT")
scanCallback?.invoke(scanResult)
} else {
scanCallback?.invoke(null)
}
scanCallback = null
}
/**
* Launch the QR code scanner activity.
*
* @param onResult Callback invoked with the scan result (null if cancelled or failed)
*/
fun launch(onResult: (String?) -> Unit) {
scanCallback = onResult
scanLauncher.launch(Intent(activity, ScannerActivity::class.java))
}
}
@@ -188,9 +188,7 @@ class BackupActivity : BaseActivity() {
} }
private fun restoreViaLocal() { private fun restoreViaLocal() {
checkAndRequestPermission(PermissionType.READ_STORAGE) { showFileChooser()
showFileChooser()
}
} }
private fun backupViaWebDav() { private fun backupViaWebDav() {
@@ -23,6 +23,7 @@ import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.dto.PermissionType import com.v2ray.ang.dto.PermissionType
import com.v2ray.ang.helper.FileChooserHelper import com.v2ray.ang.helper.FileChooserHelper
import com.v2ray.ang.helper.PermissionHelper import com.v2ray.ang.helper.PermissionHelper
import com.v2ray.ang.helper.QRCodeScannerHelper
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
@@ -42,11 +43,13 @@ abstract class BaseActivity : AppCompatActivity() {
private var progressBar: LinearProgressIndicator? = null private var progressBar: LinearProgressIndicator? = null
private lateinit var fileChooser : FileChooserHelper private lateinit var fileChooser : FileChooserHelper
private lateinit var permissionRequester : PermissionHelper private lateinit var permissionRequester : PermissionHelper
private lateinit var qrCodeScanner : QRCodeScannerHelper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
fileChooser = FileChooserHelper(this) fileChooser = FileChooserHelper(this)
permissionRequester = PermissionHelper(this) permissionRequester = PermissionHelper(this)
qrCodeScanner = QRCodeScannerHelper(this)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (!Utils.getDarkModeStatus(this)) { if (!Utils.getDarkModeStatus(this)) {
@@ -247,6 +250,20 @@ abstract class BaseActivity : AppCompatActivity() {
mimeType: String = "*/*", mimeType: String = "*/*",
onResult: (Uri?) -> Unit onResult: (Uri?) -> Unit
) { ) {
fileChooser.launch(mimeType, onResult) checkAndRequestPermission(PermissionType.READ_STORAGE) {
fileChooser.launch(mimeType, onResult)
}
}
/**
* Launch QR code scanner with camera permission check.
* Convenience method that delegates to qrCodeScanner helper.
*
* @param onResult Callback invoked with the scan result string (null if cancelled or failed)
*/
protected fun launchQRCodeScanner(onResult: (String?) -> Unit) {
checkAndRequestPermission(PermissionType.CAMERA) {
qrCodeScanner.launch(onResult)
}
} }
} }
@@ -64,12 +64,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
@@ -370,8 +364,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import config from qrcode * import config from qrcode
*/ */
private fun importQRcode(): Boolean { private fun importQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) { launchQRCodeScanner { scanResult ->
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java)) if (scanResult != null) {
importBatchConfig(scanResult)
}
} }
return true return true
} }
@@ -425,9 +421,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
*/ */
private fun importConfigLocal(): Boolean { private fun importConfigLocal(): Boolean {
try { try {
checkAndRequestPermission(PermissionType.READ_STORAGE) { showFileChooser()
showFileChooser()
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to import config from local file", e) Log.e(AppConfig.TAG, "Failed to import config from local file", e)
return false return false
@@ -6,7 +6,6 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -150,8 +149,10 @@ class RoutingSettingActivity : BaseActivity() {
} }
private fun importQRcode(): Boolean { private fun importQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) { launchQRCodeScanner { scanResult ->
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java)) if (scanResult != null) {
importRulesetsFromQRcode(scanResult)
}
} }
return true return true
} }
@@ -166,11 +167,6 @@ class RoutingSettingActivity : BaseActivity() {
} }
} }
private val scanQRcodeForRulesets = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importRulesetsFromQRcode(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private fun importRulesetsFromQRcode(qrcode: String?): Boolean { private fun importRulesetsFromQRcode(qrcode: String?): Boolean {
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip) AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
@@ -2,12 +2,10 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.dto.PermissionType
class ScScannerActivity : BaseActivity() { class ScScannerActivity : BaseActivity() {
@@ -17,26 +15,20 @@ class ScScannerActivity : BaseActivity() {
importQRcode() importQRcode()
} }
private fun importQRcode(): Boolean { private fun importQRcode() {
checkAndRequestPermission(PermissionType.CAMERA) { launchQRCodeScanner { scanResult ->
scanQRCode.launch(Intent(this, ScannerActivity::class.java)) if (scanResult != null) {
} val (count, countSub) = AngConfigManager.importBatchConfig(scanResult, "", false)
return true
}
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (count + countSub > 0) {
if (it.resultCode == RESULT_OK) { toastSuccess(R.string.toast_success)
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty() } else {
val (count, countSub) = AngConfigManager.importBatchConfig(scanResult, "", false) toastError(R.string.toast_failure)
}
if (count + countSub > 0) { startActivity(Intent(this, MainActivity::class.java))
toastSuccess(R.string.toast_success)
} else {
toastError(R.string.toast_failure)
} }
finish()
startActivity(Intent(this, MainActivity::class.java))
} }
finish()
} }
} }
@@ -72,9 +72,7 @@ class ScannerActivity : BaseActivity() {
} }
R.id.select_photo -> { R.id.select_photo -> {
checkAndRequestPermission(PermissionType.READ_STORAGE) { showFileChooser()
showFileChooser()
}
true true
} }
@@ -9,7 +9,6 @@ import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@@ -91,12 +90,6 @@ class UserAssetActivity : BaseActivity() {
} }
private fun showFileChooser() { private fun showFileChooser() {
checkAndRequestPermission(PermissionType.READ_STORAGE) {
showFileChooser2()
}
}
private fun showFileChooser2() {
launchFileChooser { uri -> launchFileChooser { uri ->
if (uri == null) { if (uri == null) {
return@launchFileChooser return@launchFileChooser
@@ -148,17 +141,14 @@ class UserAssetActivity : BaseActivity() {
} }
private fun importAssetFromQRcode(): Boolean { private fun importAssetFromQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) { launchQRCodeScanner { scanResult ->
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java)) if (scanResult != null) {
importAsset(scanResult)
}
} }
return true return true
} }
private val scanQRCodeForAssetURL = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importAsset(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private fun importAsset(url: String?): Boolean { private fun importAsset(url: String?): Boolean {
try { try {