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() {
checkAndRequestPermission(PermissionType.READ_STORAGE) {
showFileChooser()
}
showFileChooser()
}
private fun backupViaWebDav() {
@@ -23,6 +23,7 @@ import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.dto.PermissionType
import com.v2ray.ang.helper.FileChooserHelper
import com.v2ray.ang.helper.PermissionHelper
import com.v2ray.ang.helper.QRCodeScannerHelper
import com.v2ray.ang.util.Utils
@@ -42,11 +43,13 @@ abstract class BaseActivity : AppCompatActivity() {
private var progressBar: LinearProgressIndicator? = null
private lateinit var fileChooser : FileChooserHelper
private lateinit var permissionRequester : PermissionHelper
private lateinit var qrCodeScanner : QRCodeScannerHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fileChooser = FileChooserHelper(this)
permissionRequester = PermissionHelper(this)
qrCodeScanner = QRCodeScannerHelper(this)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (!Utils.getDarkModeStatus(this)) {
@@ -247,6 +250,20 @@ abstract class BaseActivity : AppCompatActivity() {
mimeType: String = "*/*",
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?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
@@ -370,8 +364,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import config from qrcode
*/
private fun importQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) {
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
launchQRCodeScanner { scanResult ->
if (scanResult != null) {
importBatchConfig(scanResult)
}
}
return true
}
@@ -425,9 +421,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
*/
private fun importConfigLocal(): Boolean {
try {
checkAndRequestPermission(PermissionType.READ_STORAGE) {
showFileChooser()
}
showFileChooser()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to import config from local file", e)
return false
@@ -6,7 +6,6 @@ import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
@@ -150,8 +149,10 @@ class RoutingSettingActivity : BaseActivity() {
}
private fun importQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) {
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
launchQRCodeScanner { scanResult ->
if (scanResult != null) {
importRulesetsFromQRcode(scanResult)
}
}
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 {
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.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.v2ray.ang.R
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.dto.PermissionType
class ScScannerActivity : BaseActivity() {
@@ -17,26 +15,20 @@ class ScScannerActivity : BaseActivity() {
importQRcode()
}
private fun importQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) {
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
}
return true
}
private fun importQRcode() {
launchQRCodeScanner { scanResult ->
if (scanResult != null) {
val (count, countSub) = AngConfigManager.importBatchConfig(scanResult, "", false)
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty()
val (count, countSub) = AngConfigManager.importBatchConfig(scanResult, "", false)
if (count + countSub > 0) {
toastSuccess(R.string.toast_success)
} else {
toastError(R.string.toast_failure)
}
if (count + countSub > 0) {
toastSuccess(R.string.toast_success)
} else {
toastError(R.string.toast_failure)
startActivity(Intent(this, MainActivity::class.java))
}
startActivity(Intent(this, MainActivity::class.java))
finish()
}
finish()
}
}
@@ -72,9 +72,7 @@ class ScannerActivity : BaseActivity() {
}
R.id.select_photo -> {
checkAndRequestPermission(PermissionType.READ_STORAGE) {
showFileChooser()
}
showFileChooser()
true
}
@@ -9,7 +9,6 @@ import android.provider.OpenableColumns
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
@@ -91,12 +90,6 @@ class UserAssetActivity : BaseActivity() {
}
private fun showFileChooser() {
checkAndRequestPermission(PermissionType.READ_STORAGE) {
showFileChooser2()
}
}
private fun showFileChooser2() {
launchFileChooser { uri ->
if (uri == null) {
return@launchFileChooser
@@ -148,17 +141,14 @@ class UserAssetActivity : BaseActivity() {
}
private fun importAssetFromQRcode(): Boolean {
checkAndRequestPermission(PermissionType.CAMERA) {
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
launchQRCodeScanner { scanResult ->
if (scanResult != null) {
importAsset(scanResult)
}
}
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 {
try {