diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt index 4f171f3d..912b8d2f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyActivity.kt @@ -7,6 +7,7 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.Toast +import androidx.activity.viewModels import androidx.appcompat.widget.SearchView import androidx.lifecycle.lifecycleScope import com.v2ray.ang.AppConfig @@ -23,6 +24,7 @@ import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.util.AppManagerUtil import com.v2ray.ang.util.HttpUtil import com.v2ray.ang.util.Utils +import com.v2ray.ang.viewmodel.PerAppProxyViewModel import es.dmoral.toasty.Toasty import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -34,6 +36,7 @@ class PerAppProxyActivity : BaseActivity() { private var adapter: PerAppProxyAdapter? = null private var appsAll: List? = null + private val viewModel: PerAppProxyViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -64,14 +67,13 @@ class PerAppProxyActivity : BaseActivity() { lifecycleScope.launch { try { - val blacklist = - MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET) val apps = withContext(Dispatchers.IO) { val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity) - if (blacklist != null) { + val blacklistSet = viewModel.getAll() + if (blacklistSet.isNotEmpty()) { appsList.forEach { app -> - app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0 + app.isSelected = if (blacklistSet.contains(app.packageName)) 1 else 0 } appsList.sortedWith { p1, p2 -> when { @@ -93,8 +95,9 @@ class PerAppProxyActivity : BaseActivity() { } appsAll = apps - adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist) + adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, viewModel) binding.recyclerView.adapter = adapter + } catch (e: Exception) { Log.e(ANG_PACKAGE, "Error loading apps", e) } finally { @@ -103,13 +106,6 @@ class PerAppProxyActivity : BaseActivity() { } } - override fun onPause() { - super.onPause() - adapter?.let { - MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist) - } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_bypass_list, menu) @@ -164,33 +160,23 @@ class PerAppProxyActivity : BaseActivity() { } private fun selectAllApp() { - adapter?.let { it -> - val pkgNames = it.apps.map { it.packageName } - if (it.blacklist.containsAll(pkgNames)) { - it.apps.forEach { - val packageName = it.packageName - adapter?.blacklist?.remove(packageName) - } + adapter?.let { adapter -> + val pkgNames = adapter.apps.map { it.packageName } + val allSelected = pkgNames.all { viewModel.contains(it) } + + if (allSelected) { + viewModel.removeAll(pkgNames) } else { - it.apps.forEach { - val packageName = it.packageName - adapter?.blacklist?.add(packageName) - } + viewModel.addAll(pkgNames) } - it.notifyDataSetChanged() - true + refreshData() } } private fun invertSelection() { adapter?.let { adapter -> adapter.apps.forEach { app -> - val packageName = app.packageName - if (adapter.blacklist.contains(packageName)) { - adapter.blacklist.remove(packageName) - } else { - adapter.blacklist.add(packageName) - } + viewModel.toggle(app.packageName) } refreshData() } @@ -226,8 +212,8 @@ class PerAppProxyActivity : BaseActivity() { private fun exportProxyApp() { var lst = binding.switchBypassApps.isChecked.toString() - adapter?.blacklist?.forEach block@{ - lst = lst + System.lineSeparator() + it + viewModel.getAll().forEach { pkg -> + lst = lst + System.lineSeparator() + pkg } Utils.setClipboard(applicationContext, lst) toastSuccess(R.string.toast_success) @@ -248,29 +234,27 @@ class PerAppProxyActivity : BaseActivity() { } if (TextUtils.isEmpty(proxyApps)) return false - adapter?.blacklist?.clear() + viewModel.clear() if (binding.switchBypassApps.isChecked) { - adapter?.let { it -> - it.apps.forEach block@{ - val packageName = it.packageName + adapter?.let { adapter -> + adapter.apps.forEach { app -> + val packageName = app.packageName if (!inProxyApps(proxyApps, packageName, force)) { - adapter?.blacklist?.add(packageName) - return@block + viewModel.add(packageName) } } - it.notifyDataSetChanged() + refreshData() } } else { - adapter?.let { it -> - it.apps.forEach block@{ - val packageName = it.packageName + adapter?.let { adapter -> + adapter.apps.forEach { app -> + val packageName = app.packageName if (inProxyApps(proxyApps, packageName, force)) { - adapter?.blacklist?.add(packageName) - return@block + viewModel.add(packageName) } } - it.notifyDataSetChanged() + refreshData() } } } catch (e: Exception) { @@ -308,7 +292,7 @@ class PerAppProxyActivity : BaseActivity() { } } - adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist) + adapter = PerAppProxyAdapter(this, apps, adapter?.viewModel ?: viewModel) binding.recyclerView.adapter = adapter refreshData() return true diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyAdapter.kt index 4ae3ca18..f9604ea6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/PerAppProxyAdapter.kt @@ -6,9 +6,9 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding import com.v2ray.ang.dto.AppInfo -import com.v2ray.ang.handler.SettingsChangeManager +import com.v2ray.ang.viewmodel.PerAppProxyViewModel -class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, blacklist: MutableSet?) : +class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, val viewModel: PerAppProxyViewModel) : RecyclerView.Adapter() { companion object { @@ -16,8 +16,6 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl private const val VIEW_TYPE_ITEM = 1 } - val blacklist = if (blacklist == null) HashSet() else HashSet(blacklist) - override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { if (holder is AppViewHolder) { val appInfo = apps[position - 1] @@ -39,11 +37,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl ) BaseViewHolder(view) } -// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater -// .inflate(R.layout.item_recycler_bypass_list, parent, false)) - else -> AppViewHolder(ItemRecyclerBypassListBinding.inflate(LayoutInflater.from(ctx), parent, false)) - } } @@ -53,13 +47,11 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl inner class AppViewHolder(private val itemBypassBinding: ItemRecyclerBypassListBinding) : BaseViewHolder(itemBypassBinding.root), View.OnClickListener { - private val inBlacklist: Boolean get() = blacklist.contains(appInfo.packageName) private lateinit var appInfo: AppInfo fun bind(appInfo: AppInfo) { this.appInfo = appInfo - // Set app icon and name itemBypassBinding.icon.setImageDrawable(appInfo.appIcon) itemBypassBinding.name.text = if (appInfo.isSystemApp) { String.format("** %s", appInfo.appName) @@ -67,24 +59,16 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl appInfo.appName } - // Set package name and checkbox state itemBypassBinding.packageName.text = appInfo.packageName - itemBypassBinding.checkBox.isChecked = inBlacklist + itemBypassBinding.checkBox.isChecked = viewModel.contains(appInfo.packageName) - // Handle item click to toggle blacklist status itemView.setOnClickListener(this) } - override fun onClick(v: View?) { - if (inBlacklist) { - blacklist.remove(appInfo.packageName) - itemBypassBinding.checkBox.isChecked = false - } else { - blacklist.add(appInfo.packageName) - itemBypassBinding.checkBox.isChecked = true - } - SettingsChangeManager.makeRestartService() + val packageName = appInfo.packageName + viewModel.toggle(packageName) + itemBypassBinding.checkBox.isChecked = viewModel.contains(packageName) } } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt new file mode 100644 index 00000000..83b63b8c --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt @@ -0,0 +1,64 @@ +package com.v2ray.ang.viewmodel + +import androidx.lifecycle.ViewModel +import com.v2ray.ang.AppConfig +import com.v2ray.ang.handler.MmkvManager +import com.v2ray.ang.handler.SettingsChangeManager + +class PerAppProxyViewModel : ViewModel() { + private val blacklist: MutableSet = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)?.let { + HashSet(it) + } ?: HashSet() + + fun contains(packageName: String): Boolean = blacklist.contains(packageName) + + fun getAll(): Set = blacklist.toSet() + + fun add(packageName: String): Boolean { + val changed = blacklist.add(packageName) + if (changed) { + save() + } + return changed + } + + fun remove(packageName: String): Boolean { + val changed = blacklist.remove(packageName) + if (changed) { + save() + } + return changed + } + + fun toggle(packageName: String) { + if (blacklist.contains(packageName)) { + remove(packageName) + } else { + add(packageName) + } + } + + fun addAll(packages: Collection) { + if (blacklist.addAll(packages)) { + save() + } + } + + fun removeAll(packages: Collection) { + if (blacklist.removeAll(packages.toSet())) { + save() + } + } + + fun clear() { + if (blacklist.isNotEmpty()) { + blacklist.clear() + save() + } + } + + private fun save() { + MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY_SET, blacklist) + SettingsChangeManager.makeRestartService() + } +} \ No newline at end of file