From 9743d7b87b2c42d30efffb6fa3588894ddbc189d Mon Sep 17 00:00:00 2001 From: hhhkkmk <201595863+hhhkkmk@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:05:52 +0800 Subject: [PATCH] update (#4365) --- V2rayNG/app/build.gradle.kts | 5 +- .../v2ray/ang/service/V2RayServiceManager.kt | 26 +-- .../java/com/v2ray/ang/ui/AboutActivity.kt | 72 +++++--- .../java/com/v2ray/ang/ui/MainActivity.kt | 164 ++++++++++-------- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 18 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 137 +++------------ .../v2ray/ang/ui/RoutingSettingActivity.kt | 28 ++- .../com/v2ray/ang/ui/ScScannerActivity.kt | 26 +-- .../java/com/v2ray/ang/ui/ScannerActivity.kt | 74 ++++---- .../com/v2ray/ang/ui/UserAssetActivity.kt | 66 +++---- .../java/com/v2ray/ang/util/AppManagerUtil.kt | 45 ++--- V2rayNG/gradle/libs.versions.toml | 12 +- 12 files changed, 318 insertions(+), 355 deletions(-) diff --git a/V2rayNG/app/build.gradle.kts b/V2rayNG/app/build.gradle.kts index 0e88941a..02ea3fad 100644 --- a/V2rayNG/app/build.gradle.kts +++ b/V2rayNG/app/build.gradle.kts @@ -157,9 +157,8 @@ dependencies { implementation(libs.gson) // Reactive and Utility Libraries - implementation(libs.rxjava) - implementation(libs.rxandroid) - implementation(libs.rxpermissions) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.coroutines.core) // Language and Processing Libraries implementation(libs.language.base) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt index b94c7a10..0de09c2f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt @@ -31,10 +31,11 @@ import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.PluginUtil import com.v2ray.ang.util.Utils import go.Seq -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import libv2ray.Libv2ray import libv2ray.V2RayPoint @@ -62,7 +63,7 @@ object V2RayServiceManager { private var lastQueryTime = 0L private var mBuilder: NotificationCompat.Builder? = null - private var mDisposable: Disposable? = null + private var speedNotificationJob: Job? = null private var mNotificationManager: NotificationManager? = null fun startV2Ray(context: Context) { @@ -365,8 +366,8 @@ object V2RayServiceManager { } mBuilder = null - mDisposable?.dispose() - mDisposable = null + speedNotificationJob?.cancel() + speedNotificationJob = null } private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) { @@ -393,7 +394,7 @@ object V2RayServiceManager { } private fun startSpeedNotification() { - if (mDisposable == null && + if (speedNotificationJob == null && v2rayPoint.isRunning && MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) == true ) { @@ -401,8 +402,8 @@ object V2RayServiceManager { val outboundTags = currentConfig?.getAllOutboundTags() outboundTags?.remove(TAG_DIRECT) - mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) - .subscribe { + speedNotificationJob = CoroutineScope(Dispatchers.IO).launch { + while (isActive) { val queryTime = System.currentTimeMillis() val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0 var proxyTotal = 0L @@ -430,7 +431,9 @@ object V2RayServiceManager { } lastZeroSpeed = zeroSpeed lastQueryTime = queryTime + delay(3000) } + } } } @@ -445,11 +448,10 @@ object V2RayServiceManager { } private fun stopSpeedNotification() { - mDisposable?.let { - it.dispose() //stop queryStats - mDisposable = null + speedNotificationJob?.let { + it.cancel() + speedNotificationJob = null updateNotification(currentConfig?.remarks, 0, 0) } } - } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt index eacacb35..806758af 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt @@ -5,15 +5,16 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.util.Log +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.content.FileProvider -import com.tbruyelle.rxpermissions3.RxPermissions import com.tencent.mmkv.MMKV import com.v2ray.ang.AppConfig import com.v2ray.ang.BuildConfig import com.v2ray.ang.R import com.v2ray.ang.databinding.ActivityAboutBinding -import com.v2ray.ang.extension.toast import com.v2ray.ang.util.SpeedtestUtil import com.v2ray.ang.util.Utils import com.v2ray.ang.util.ZipUtil @@ -21,11 +22,24 @@ import java.io.File import java.text.SimpleDateFormat import java.util.Locale - class AboutActivity : BaseActivity() { + private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) } private val extDir by lazy { File(Utils.backupPath(this)) } + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + try { + showFileChooser() + } catch (e: Exception) { + e.printStackTrace() + } + } else { + toast(R.string.toast_permission_denied) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -33,6 +47,7 @@ class AboutActivity : BaseActivity() { title = getString(R.string.title_about) binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir) + binding.layoutBackup.setOnClickListener { val ret = backupConfiguration(extDir.absolutePath) if (ret.first) { @@ -50,7 +65,8 @@ class AboutActivity : BaseActivity() { Intent(Intent.ACTION_SEND).setType("application/zip") .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .putExtra( - Intent.EXTRA_STREAM, FileProvider.getUriForFile( + Intent.EXTRA_STREAM, + FileProvider.getUriForFile( this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second) ) ), getString(R.string.title_configuration_share) @@ -62,23 +78,22 @@ class AboutActivity : BaseActivity() { } binding.layoutRestore.setOnClickListener { - val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - Manifest.permission.READ_MEDIA_IMAGES - } else { - Manifest.permission.READ_EXTERNAL_STORAGE - } - RxPermissions(this) - .request(permission) - .subscribe { - if (it) { - try { - showFileChooser() - } catch (e: Exception) { - e.printStackTrace() - } - } else - toast(R.string.toast_permission_denied) + val permission = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE } + + if (ContextCompat.checkSelfPermission(this, permission) == android.content.pm.PackageManager.PERMISSION_GRANTED) { + try { + showFileChooser() + } catch (e: Exception) { + e.printStackTrace() + } + } else { + requestPermissionLauncher.launch(permission) + } } binding.layoutSoureCcode.setOnClickListener { @@ -88,13 +103,15 @@ class AboutActivity : BaseActivity() { binding.layoutFeedback.setOnClickListener { Utils.openUri(this, AppConfig.v2rayNGIssues) } - binding.layoutOssLicenses.setOnClickListener{ + + binding.layoutOssLicenses.setOnClickListener { val webView = android.webkit.WebView(this); - webView.loadUrl("file:///android_asset/open_source_licenses.html"); + webView.loadUrl("file:///android_asset/open_source_licenses.html") android.app.AlertDialog.Builder(this) .setTitle("Open source licenses") .setView(webView) - .setPositiveButton("OK", android.content.DialogInterface.OnClickListener { dialog, whichButton -> dialog.dismiss() }).show() + .setPositiveButton("OK") { dialog, _ -> dialog.dismiss() } + .show() } binding.layoutTgChannel.setOnClickListener { @@ -157,9 +174,9 @@ class AboutActivity : BaseActivity() { } private val chooseFile = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val uri = it.data?.data - if (it.resultCode == RESULT_OK && uri != null) { + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + val uri = result.data?.data + if (result.resultCode == RESULT_OK && uri != null) { try { val targetFile = File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip") @@ -180,4 +197,7 @@ class AboutActivity : BaseActivity() { } } + private fun toast(messageResId: Int) { + Toast.makeText(this, getString(messageResId), Toast.LENGTH_SHORT).show() + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt index 53ae34ea..8b7b336c 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt @@ -3,6 +3,7 @@ package com.v2ray.ang.ui import android.Manifest import android.content.ActivityNotFoundException import android.content.Intent +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.net.Uri import android.net.VpnService @@ -27,7 +28,6 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.navigation.NavigationView import com.google.android.material.tabs.TabLayout -import com.tbruyelle.rxpermissions3.RxPermissions import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.VPN import com.v2ray.ang.R @@ -41,8 +41,6 @@ import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.util.Utils import com.v2ray.ang.viewmodel.MainViewModel -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -81,6 +79,60 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList private var mItemTouchHelper: ItemTouchHelper? = null val mainViewModel: MainViewModel by viewModels() + // register activity result for requesting permission + private val requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + when (pendingAction) { + Action.IMPORT_QR_CODE_CONFIG -> + scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java)) + Action.IMPORT_QR_CODE_URL -> + scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java)) + Action.READ_CONTENT_FROM_URI -> + chooseFileForCustomConfig.launch(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply { + type = "*/*" + addCategory(Intent.CATEGORY_OPENABLE) + }, getString(R.string.title_file_chooser))) + Action.POST_NOTIFICATIONS -> {} + else -> {} + } + } else { + toast(R.string.toast_permission_denied) + } + pendingAction = Action.NONE + } + + private var pendingAction: Action = Action.NONE + + enum class Action { + NONE, + IMPORT_QR_CODE_CONFIG, + IMPORT_QR_CODE_URL, + READ_CONTENT_FROM_URI, + POST_NOTIFICATIONS + } + + private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val uri = it.data?.data + if (it.resultCode == RESULT_OK && uri != null) { + readContentFromUri(uri) + } + } + + private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + importBatchConfig(it.data?.getStringExtra("SCAN_RESULT")) + } + } + + private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT")) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -129,12 +181,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList migrateLegacy() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - RxPermissions(this) - .request(Manifest.permission.POST_NOTIFICATIONS) - .subscribe { - if (!it) - toast(R.string.toast_permission_denied_notification) - } + if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + pendingAction = Action.POST_NOTIFICATIONS + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } } onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { @@ -226,11 +276,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList if (mainViewModel.isRunning.value == true) { Utils.stopVService(this) } - Observable.timer(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - startV2Ray() - } + lifecycleScope.launch { + delay(500) + startV2Ray() + } } public override fun onResume() { @@ -462,38 +511,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList * import config from qrcode */ private fun importQRcode(forConfig: Boolean): Boolean { -// try { -// startActivityForResult(Intent("com.google.zxing.client.android.SCAN") -// .addCategory(Intent.CATEGORY_DEFAULT) -// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) -// } catch (e: Exception) { - RxPermissions(this) - .request(Manifest.permission.CAMERA) - .subscribe { - if (it) - if (forConfig) - scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java)) - else - scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java)) - else - toast(R.string.toast_permission_denied) + val permission = Manifest.permission.CAMERA + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { + if (forConfig) { + scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java)) + } else { + scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java)) } -// } + } else { + pendingAction = if (forConfig) Action.IMPORT_QR_CODE_CONFIG else Action.IMPORT_QR_CODE_URL + requestPermissionLauncher.launch(permission) + } return true } - private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - importBatchConfig(it.data?.getStringExtra("SCAN_RESULT")) - } - } - - private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT")) - } - } - /** * import config from clipboard */ @@ -614,10 +645,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList * import config from sub */ private fun importConfigViaSub(): Boolean { -// val dialog = AlertDialog.Builder(this) -// .setView(LayoutProgressBinding.inflate(layoutInflater).root) -// .setCancelable(false) -// .show() binding.pbWaiting.show() lifecycleScope.launch(Dispatchers.IO) { @@ -630,7 +657,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } else { toast(R.string.toast_failure) } - //dialog.dismiss() binding.pbWaiting.hide() } } @@ -645,17 +671,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList intent.type = "*/*" intent.addCategory(Intent.CATEGORY_OPENABLE) - try { - chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser))) - } catch (ex: ActivityNotFoundException) { - toast(R.string.toast_require_file_manager) + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE } - } - private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val uri = it.data?.data - if (it.resultCode == RESULT_OK && uri != null) { - readContentFromUri(uri) + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { + pendingAction = Action.READ_CONTENT_FROM_URI + chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser))) + } else { + requestPermissionLauncher.launch(permission) } } @@ -668,20 +694,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } else { Manifest.permission.READ_EXTERNAL_STORAGE } - RxPermissions(this) - .request(permission) - .subscribe { - if (it) { - try { - contentResolver.openInputStream(uri).use { input -> - importCustomizeConfig(input?.bufferedReader()?.readText()) - } - } catch (e: Exception) { - e.printStackTrace() - } - } else - toast(R.string.toast_permission_denied) + + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { + try { + contentResolver.openInputStream(uri).use { input -> + importCustomizeConfig(input?.bufferedReader()?.readText()) + } + } catch (e: Exception) { + e.printStackTrace() } + } else { + requestPermissionLauncher.launch(permission) + } } /** @@ -763,4 +787,4 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList binding.drawerLayout.closeDrawer(GravityCompat.START) return true } -} +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 30bc3ed2..9801cd90 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.v2ray.ang.AngApplication.Companion.application import com.v2ray.ang.AppConfig @@ -23,9 +24,9 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.util.Utils -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import java.util.concurrent.TimeUnit +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.util.* class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter(), ItemTouchHelperAdapter { companion object { @@ -165,11 +166,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter - if (blacklist.contains(one.packageName)) { - one.isSelected = 1 - } else { - one.isSelected = 0 + lifecycleScope.launch { + try { + binding.pbWaiting.visibility = View.VISIBLE + val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET) + val apps = withContext(Dispatchers.IO) { + val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity) + + if (blacklist != null) { + appsList.forEach { app -> + app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0 } - } - val comparator = Comparator { p1, p2 -> - when { - p1.isSelected > p2.isSelected -> -1 - p1.isSelected == p2.isSelected -> 0 - else -> 1 - } - } - it.sortedWith(comparator) - } else { - val comparator = object : Comparator { + appsList.sortedWith(Comparator { p1, p2 -> + when { + p1.isSelected > p2.isSelected -> -1 + p1.isSelected == p2.isSelected -> 0 + else -> 1 + } + }) + } else { val collator = Collator.getInstance() - override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName) + appsList.sortedWith(compareBy(collator) { it.appName }) } - it.sortedWith(comparator) } - } -// .map { -// val comparator = object : Comparator { -// val collator = Collator.getInstance() -// override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName) -// } -// it.sortedWith(comparator) -// } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - appsAll = it - adapter = PerAppProxyAdapter(this, it, blacklist) + + appsAll = apps + adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist) binding.recyclerView.adapter = adapter binding.pbWaiting.visibility = View.GONE + } catch (e: Exception) { + binding.pbWaiting.visibility = View.GONE + Log.e(ANG_PACKAGE, "Error loading apps", e) } - /*** - recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { - var dst = 0 - val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2 - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - dst += dy - if (dst > threshold) { - header_view.hide() - dst = 0 - } else if (dst < -20) { - header_view.show() - dst = 0 } - } - - var hiding = false - fun View.hide() { - val target = -height.toFloat() - if (hiding || translationY == target) return - animate() - .translationY(target) - .setInterpolator(AccelerateInterpolator(2F)) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - hiding = false - } - }) - hiding = true - } - - var showing = false - fun View.show() { - val target = 0f - if (showing || translationY == target) return - animate() - .translationY(target) - .setInterpolator(DecelerateInterpolator(2F)) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - showing = false - } - }) - showing = true - } - }) - ***/ binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked -> MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked) @@ -140,36 +85,6 @@ class PerAppProxyActivity : BaseActivity() { MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked) } binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false) - - /*** - et_search.setOnEditorActionListener { v, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - //hide - var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS) - - val key = v.text.toString().toUpperCase() - val apps = ArrayList() - if (TextUtils.isEmpty(key)) { - appsAll?.forEach { - apps.add(it) - } - } else { - appsAll?.forEach { - if (it.appName.toUpperCase().indexOf(key) >= 0) { - apps.add(it) - } - } - } - adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist) - recycler_view.adapter = adapter - adapter?.notifyDataSetChanged() - true - } else { - false - } - } - ***/ } override fun onPause() { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/RoutingSettingActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/RoutingSettingActivity.kt index 44410d67..5681f75d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/RoutingSettingActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/RoutingSettingActivity.kt @@ -12,7 +12,6 @@ import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -import com.tbruyelle.rxpermissions3.RxPermissions import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.databinding.ActivityRoutingSettingBinding @@ -39,6 +38,16 @@ class RoutingSettingActivity : BaseActivity() { private val preset_rulesets: Array by lazy { resources.getStringArray(R.array.preset_rulesets) } + + private val requestCameraPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java)) + } else { + toast(R.string.toast_permission_denied) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -102,11 +111,9 @@ class RoutingSettingActivity : BaseActivity() { e.printStackTrace() } }.show() - - } .setNegativeButton(android.R.string.cancel) { _, _ -> - //do noting + //do nothing } .show() true @@ -142,18 +149,10 @@ class RoutingSettingActivity : BaseActivity() { } R.id.import_rulesets_from_qrcode -> { - RxPermissions(this) - .request(Manifest.permission.CAMERA) - .subscribe { - if (it) - scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java)) - else - toast(R.string.toast_permission_denied) - } + requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) true } - R.id.export_rulesets_to_clipboard -> { val rulesetList = MmkvManager.decodeRoutingRulesets() if (rulesetList.isNullOrEmpty()) { @@ -201,5 +200,4 @@ class RoutingSettingActivity : BaseActivity() { rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf()) adapter.notifyDataSetChanged() } - -} +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScScannerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScScannerActivity.kt index 187a6de3..0d9a7d22 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScScannerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScScannerActivity.kt @@ -4,13 +4,23 @@ import android.Manifest import android.content.Intent import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts -import com.tbruyelle.rxpermissions3.RxPermissions import com.v2ray.ang.R import com.v2ray.ang.extension.toast import com.v2ray.ang.handler.AngConfigManager class ScScannerActivity : BaseActivity() { + private val requestCameraPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + scanQRCode.launch(Intent(this, ScannerActivity::class.java)) + } else { + toast(R.string.toast_permission_denied) + finish() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_none) @@ -18,19 +28,10 @@ class ScScannerActivity : BaseActivity() { } fun importQRcode(): Boolean { - RxPermissions(this) - .request(Manifest.permission.CAMERA) - .subscribe { granted -> - if (granted) { - scanQRCode.launch(Intent(this, ScannerActivity::class.java)) - } else { - toast(R.string.toast_permission_denied) - } - } + requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) return true } - private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == RESULT_OK) { val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty() @@ -46,5 +47,4 @@ class ScScannerActivity : BaseActivity() { } finish() } - -} +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScannerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScannerActivity.kt index 6402c5a2..829d345d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScannerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScannerActivity.kt @@ -2,13 +2,15 @@ package com.v2ray.ang.ui import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.activity.result.contract.ActivityResultContracts -import com.tbruyelle.rxpermissions3.RxPermissions +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.extension.toast @@ -21,6 +23,37 @@ import io.github.g00fy2.quickie.config.ScannerConfig class ScannerActivity : BaseActivity() { private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult) + private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val uri = it.data?.data + if (it.resultCode == RESULT_OK && uri != null) { + try { + val inputStream = contentResolver.openInputStream(uri) + val bitmap = BitmapFactory.decodeStream(inputStream) + inputStream?.close() + + val text = QRCodeDecoder.syncDecodeQRCode(bitmap) + if (text.isNullOrEmpty()) { + toast(R.string.toast_decoding_failed) + } else { + finished(text) + } + } catch (e: Exception) { + e.printStackTrace() + toast(R.string.toast_decoding_failed) + } + } + } + + private val requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + showFileChooser() + } else { + toast(R.string.toast_permission_denied) + } + } public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -72,15 +105,12 @@ class ScannerActivity : BaseActivity() { } else { Manifest.permission.READ_EXTERNAL_STORAGE } - RxPermissions(this) - .request(permission) - .subscribe { granted -> - if (granted) { - showFileChooser() - } else { - toast(R.string.toast_permission_denied) - } - } + + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { + showFileChooser() + } else { + requestPermissionLauncher.launch(permission) + } true } @@ -100,26 +130,4 @@ class ScannerActivity : BaseActivity() { toast(R.string.toast_require_file_manager) } } - - private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val uri = it.data?.data - if (it.resultCode == RESULT_OK && uri != null) { - try { - val inputStream = contentResolver.openInputStream(uri) - val bitmap = BitmapFactory.decodeStream(inputStream) - inputStream?.close() - - val text = QRCodeDecoder.syncDecodeQRCode(bitmap) - if (text.isNullOrEmpty()) { - toast(R.string.toast_decoding_failed) - } else { - finished(text) - } - } catch (e: Exception) { - e.printStackTrace() - toast(R.string.toast_decoding_failed) - } - } - } - -} +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt index c59afe5b..06d371e6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/UserAssetActivity.kt @@ -19,7 +19,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.tbruyelle.rxpermissions3.RxPermissions import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.R @@ -50,6 +49,38 @@ class UserAssetActivity : BaseActivity() { val extDir by lazy { File(Utils.userAssetPath(this)) } val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat") + private val requestStoragePermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "*/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + + try { + chooseFile.launch( + Intent.createChooser( + intent, + getString(R.string.title_file_chooser) + ) + ) + } catch (ex: android.content.ActivityNotFoundException) { + toast(R.string.toast_require_file_manager) + } + } else { + toast(R.string.toast_permission_denied) + } + } + + private val requestCameraPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java)) + } else { + toast(R.string.toast_permission_denied) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -86,27 +117,7 @@ class UserAssetActivity : BaseActivity() { } else { Manifest.permission.READ_EXTERNAL_STORAGE } - RxPermissions(this) - .request(permission) - .subscribe { - if (it) { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "*/*" - intent.addCategory(Intent.CATEGORY_OPENABLE) - - try { - chooseFile.launch( - Intent.createChooser( - intent, - getString(R.string.title_file_chooser) - ) - ) - } catch (ex: android.content.ActivityNotFoundException) { - toast(R.string.toast_require_file_manager) - } - } else - toast(R.string.toast_permission_denied) - } + requestStoragePermissionLauncher.launch(permission) } val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> @@ -158,14 +169,7 @@ class UserAssetActivity : BaseActivity() { } private fun importAssetFromQRcode(): Boolean { - RxPermissions(this) - .request(Manifest.permission.CAMERA) - .subscribe { - if (it) - scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java)) - else - toast(R.string.toast_permission_denied) - } + requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) return true } @@ -349,4 +353,4 @@ class UserAssetActivity : BaseActivity() { class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) : RecyclerView.ViewHolder(itemUserAssetBinding.root) -} +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt index 8ce94751..e2300512 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AppManagerUtil.kt @@ -4,36 +4,27 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import com.v2ray.ang.dto.AppInfo -import io.reactivex.rxjava3.core.Observable +import kotlinx.coroutines.withContext +import kotlinx.coroutines.Dispatchers object AppManagerUtil { - private fun loadNetworkAppList(ctx: Context): ArrayList { - val packageManager = ctx.packageManager - val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) - val apps = ArrayList() + suspend fun loadNetworkAppList(context: Context): ArrayList = + withContext(Dispatchers.IO) { + val packageManager = context.packageManager + val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) + val apps = ArrayList() - for (pkg in packages) { - val applicationInfo = pkg.applicationInfo ?: continue + for (pkg in packages) { + val applicationInfo = pkg.applicationInfo ?: continue - val appName = applicationInfo.loadLabel(packageManager).toString() - val appIcon = applicationInfo.loadIcon(packageManager) ?: continue - val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0 + val appName = applicationInfo.loadLabel(packageManager).toString() + val appIcon = applicationInfo.loadIcon(packageManager) ?: continue + val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0 - val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0) - apps.add(appInfo) + val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0) + apps.add(appInfo) + } + + return@withContext apps } - - return apps - } - - fun rxLoadNetworkAppList(ctx: Context): Observable> = - Observable.unsafeCreate { - it.onNext(loadNetworkAppList(ctx)) - } - -// val PackageInfo.hasInternetPermission: Boolean -// get() { -// val permissions = requestedPermissions -// return permissions?.any { it == Manifest.permission.INTERNET } ?: false -// } -} +} \ No newline at end of file diff --git a/V2rayNG/gradle/libs.versions.toml b/V2rayNG/gradle/libs.versions.toml index 2a2de8f9..974aa910 100644 --- a/V2rayNG/gradle/libs.versions.toml +++ b/V2rayNG/gradle/libs.versions.toml @@ -11,12 +11,11 @@ appcompat = "1.7.0" material = "1.12.0" activity = "1.10.0" constraintlayout = "2.2.0" -mmkvStatic = "1.3.11" +mmkvStatic = "1.3.12" gson = "2.11.0" quickieFoss = "1.13.1" -rxjava = "3.1.10" -rxandroid = "3.0.2" -rxpermissions = "0.12" +kotlinx-coroutines-android = "1.10.1" +kotlinx-coroutines-core = "1.10.1" swiperefreshlayout = "1.1.0" toastcompat = "1.1.0" editorkit = "2.9.0" @@ -43,9 +42,8 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" } -rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" } -rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" } -rxpermissions = { module = "com.github.tbruyelle:rxpermissions", version.ref = "rxpermissions" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version = "kotlinx-coroutines-android" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "kotlinx-coroutines-core" } toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" } editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" } language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }