From 09461663cb4350ac9e3adb79b56bbaada7fdf62e Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:41:53 +0800 Subject: [PATCH] Migrate servers to subscription-based storage Move server list storage into SubscriptionItem.serverList and add a default subscription for ungrouped servers. Introduce AppConfig.DEFAULT_SUBSCRIPTION_ID and update MmkvManager to read legacy data, encode/decode server lists by subscription, decodeAllServerList(), and protect default subscription from deletion. Add one-time migration logic and ensureDefaultSubscription() in SettingsManager, invoke migration during app startup in AngApplication, and update call sites (MainViewModel, V2rayConfigManager, TaskerActivity, etc.) to use the new subscription-aware APIs. Preserve legacy code paths as comments and mark migration completion with a settings flag. --- .../main/java/com/v2ray/ang/AngApplication.kt | 8 +- .../src/main/java/com/v2ray/ang/AppConfig.kt | 3 + .../com/v2ray/ang/dto/SubscriptionItem.kt | 1 + .../java/com/v2ray/ang/handler/MmkvManager.kt | 144 +++++++++++++++++- .../com/v2ray/ang/handler/SettingsManager.kt | 119 ++++++++++++--- .../v2ray/ang/handler/V2rayConfigManager.kt | 6 +- .../java/com/v2ray/ang/ui/BackupActivity.kt | 3 + .../com/v2ray/ang/ui/GroupServerFragment.kt | 1 + .../java/com/v2ray/ang/ui/SubEditActivity.kt | 2 +- .../v2ray/ang/ui/SubSettingRecyclerAdapter.kt | 3 + .../java/com/v2ray/ang/ui/TaskerActivity.kt | 2 +- .../com/v2ray/ang/viewmodel/MainViewModel.kt | 55 ++++--- 12 files changed, 294 insertions(+), 53 deletions(-) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/AngApplication.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/AngApplication.kt index a5ea796c..55fc2e26 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/AngApplication.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/AngApplication.kt @@ -34,14 +34,12 @@ class AngApplication : MultiDexApplication() { MMKV.initialize(this) - // Ensure critical preference defaults are present in MMKV early - SettingsManager.ensureDefaultSettings() - SettingsManager.setNightMode() // Initialize WorkManager with the custom configuration WorkManager.initialize(this, workManagerConfiguration) - SettingsManager.initRoutingRulesets(this) - SettingsManager.migrateHysteria2PinSHA256() + // Ensure critical preference defaults are present in MMKV early + SettingsManager.initApp(this) + SettingsManager.setNightMode() es.dmoral.toasty.Toasty.Config.getInstance() .setGravity(android.view.Gravity.BOTTOM, 0, 300) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 598c3b85..5413bef0 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -16,6 +16,9 @@ object AppConfig { /** Legacy configuration keys. */ const val ANG_CONFIG = "ang_config" + // Default subscription ID for ungrouped servers + const val DEFAULT_SUBSCRIPTION_ID = "__default_subscription__" + /** Preferences mapped to MMKV storage. */ const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled" const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt index 8ab871c3..485d2b97 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt @@ -13,5 +13,6 @@ data class SubscriptionItem( var filter: String? = null, var allowInsecureUrl: Boolean = false, var userAgent: String? = null, + var serverList: MutableList = mutableListOf(), ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt index ca3c4e1d..e62d1004 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt @@ -1,6 +1,7 @@ package com.v2ray.ang.handler import com.tencent.mmkv.MMKV +import com.v2ray.ang.AppConfig.DEFAULT_SUBSCRIPTION_ID import com.v2ray.ang.AppConfig.PREF_IS_BOOTED import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET import com.v2ray.ang.dto.AssetUrlCache @@ -44,6 +45,24 @@ object MmkvManager { //region Server + /** + * Reads the legacy server list from KEY_ANG_CONFIGS for migration. + * This method is for migration purposes only. + * + * @return The JSON string of legacy server list, or null if not exists. + */ + fun readLegacyServerList(): String? { + return mainStorage.decodeString(KEY_ANG_CONFIGS) + } + +// /** +// * Removes the legacy KEY_ANG_CONFIGS after migration. +// * This method is for migration purposes only. +// */ +// fun removeLegacyServerListKey() { +// mainStorage.remove(KEY_ANG_CONFIGS) +// } + /** * Gets the selected server GUID. * @@ -63,19 +82,62 @@ object MmkvManager { } /** - * Encodes the server list. + * Encodes the server list for a given subscription. + * Saves to the subscription's serverList (including default subscription for ungrouped servers). * * @param serverList The list of server GUIDs. + * @param subscriptionId The subscription ID. */ + fun encodeServerList(serverList: MutableList, subscriptionId: String) { + val subId = getSubscriptionId(subscriptionId) + val subItem = decodeSubscription(subId) ?: return + + subItem.serverList = serverList + encodeSubscription(subId, subItem) + } + + // Legacy method for compatibility + // TODO: Remove after migration and update all callers + /* fun encodeServerList(serverList: MutableList) { mainStorage.encode(KEY_ANG_CONFIGS, JsonUtil.toJson(serverList)) } + */ /** - * Decodes the server list. + * Decodes the server list for a given subscription. + * If subscriptionId is empty, returns ungrouped servers. + * Otherwise, returns servers from the specified subscription's serverList. * + * @param subscriptionId The subscription ID. * @return The list of server GUIDs. */ + fun decodeServerList(subscriptionId: String): MutableList { + val subId = getSubscriptionId(subscriptionId) + + return decodeSubscription(subId)?.serverList ?: mutableListOf() + } + + /** + * Decodes all server list (merged from all subscriptions including default subscription). + * Use this when you need the complete server list. + * + * @return The list of all server GUIDs. + */ + fun decodeAllServerList(): MutableList { + val allServers = mutableListOf() + + // Add servers from all subscriptions (including default subscription) + decodeSubscriptions().forEach { sub -> + allServers.addAll(sub.subscription.serverList) + } + + return allServers + } + + // Legacy method for compatibility - reads all servers + // TODO: Remove after migration and update all callers + /* fun decodeServerList(): MutableList { val json = mainStorage.decodeString(KEY_ANG_CONFIGS) return if (json.isNullOrBlank()) { @@ -84,6 +146,7 @@ object MmkvManager { JsonUtil.fromJson(json, Array::class.java)?.toMutableList() ?: mutableListOf() } } + */ /** * Decodes the server configuration. @@ -123,6 +186,21 @@ object MmkvManager { fun encodeServerConfig(guid: String, config: ProfileItem): String { val key = guid.ifBlank { Utils.getUuid() } profileFullStorage.encode(key, JsonUtil.toJson(config)) + + // Use default subscription for servers without subscription + val subId = getSubscriptionId(config.subscriptionId) + val serverList = decodeServerList(subId) + + if (!serverList.contains(key)) { + serverList.add(0, key) + encodeServerList(serverList, subId) + if (getSelectServer().isNullOrBlank()) { + mainStorage.encode(KEY_SELECTED_SERVER, key) + } + } + + // Legacy code - keep for reference during migration + /* val serverList = decodeServerList() if (!serverList.contains(key)) { serverList.add(0, key) @@ -131,6 +209,7 @@ object MmkvManager { mainStorage.encode(KEY_SELECTED_SERVER, key) } } + */ // val profile = ProfileLiteItem( // configType = config.configType, // subscriptionId = config.subscriptionId, @@ -151,12 +230,30 @@ object MmkvManager { if (guid.isBlank()) { return } + + // Get config to determine which subscription to update + val config = decodeServerConfig(guid) + val subId = getSubscriptionId(config?.subscriptionId) + + // Remove from appropriate server list + val serverList = decodeServerList(subId) + serverList.remove(guid) + encodeServerList(serverList, subId) + + // Legacy code - keep for reference during migration + /* if (getSelectServer() == guid) { mainStorage.remove(KEY_SELECTED_SERVER) } val serverList = decodeServerList() serverList.remove(guid) encodeServerList(serverList) + */ + + // Clean up storage + if (getSelectServer() == guid) { + mainStorage.remove(KEY_SELECTED_SERVER) + } profileFullStorage.remove(guid) //profileStorage.remove(guid) serverAffStorage.remove(guid) @@ -167,10 +264,28 @@ object MmkvManager { * * @param subid The subscription ID. */ - fun removeServerViaSubid(subid: String) { - if (subid.isBlank()) { - return + fun removeServerViaSubid(subid: String?) { + val subId2 = getSubscriptionId(subid) + + // Get server list from subscription + val subItem = decodeSubscription(subId2) ?: return + val serverList = subItem.serverList + + // Remove all servers in the list + serverList.forEach { guid -> + if (getSelectServer() == guid) { + mainStorage.remove(KEY_SELECTED_SERVER) + } + profileFullStorage.remove(guid) + serverAffStorage.remove(guid) } + + // Clear subscription's serverList + subItem.serverList.clear() + encodeSubscription(subId2, subItem) + + // Legacy code - keep for reference during migration + /* profileFullStorage.allKeys()?.forEach { key -> decodeServerConfig(key)?.let { config -> if (config.subscriptionId == subid) { @@ -178,6 +293,7 @@ object MmkvManager { } } } + */ } /** @@ -233,13 +349,22 @@ object MmkvManager { */ fun removeAllServer(): Int { val count = profileFullStorage.allKeys()?.count() ?: 0 + + decodeSubscriptions().forEach { sub -> + sub.subscription.serverList.clear() + encodeSubscription(sub.guid, sub.subscription) + } + + // Clear all storage mainStorage.clearAll() profileFullStorage.clearAll() //profileStorage.clearAll() serverAffStorage.clearAll() + return count } + /** * Removes invalid server configurations. * @@ -292,6 +417,10 @@ object MmkvManager { //region Subscriptions + private fun getSubscriptionId(subscriptionId: String?):String { + return subscriptionId?.ifEmpty { DEFAULT_SUBSCRIPTION_ID } ?: DEFAULT_SUBSCRIPTION_ID + } + /** * Initializes the subscription list. */ @@ -331,6 +460,11 @@ object MmkvManager { * @param subid The subscription ID. */ fun removeSubscription(subid: String) { + // Protect default subscription from being deleted + if (subid == DEFAULT_SUBSCRIPTION_ID) { + return + } + subStorage.remove(subid) val subsList = decodeSubsList() subsList.remove(subid) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt index 46fdb412..9d8141bd 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsManager.kt @@ -7,20 +7,24 @@ import android.util.Log import androidx.appcompat.app.AppCompatDelegate import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_PACKAGE +import com.v2ray.ang.AppConfig.DEFAULT_SUBSCRIPTION_ID import com.v2ray.ang.AppConfig.GEOIP_PRIVATE import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.VPN +import com.v2ray.ang.dto.ProfileItem +import com.v2ray.ang.dto.RulesetItem +import com.v2ray.ang.dto.SubscriptionItem +import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.enums.EConfigType import com.v2ray.ang.enums.Language -import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.enums.RoutingType -import com.v2ray.ang.dto.RulesetItem -import com.v2ray.ang.dto.ServersCache -import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.enums.VpnInterfaceAddressConfig +import com.v2ray.ang.handler.MmkvManager.decodeAllServerList import com.v2ray.ang.handler.MmkvManager.decodeServerConfig -import com.v2ray.ang.handler.MmkvManager.decodeServerList +import com.v2ray.ang.handler.MmkvManager.decodeSubsList +import com.v2ray.ang.handler.MmkvManager.decodeSubscription +import com.v2ray.ang.handler.MmkvManager.encodeSubscription import com.v2ray.ang.util.JsonUtil import com.v2ray.ang.util.Utils import java.io.File @@ -30,11 +34,19 @@ import java.util.Locale object SettingsManager { + fun initApp(context: Context) { + ensureDefaultSettings() + ensureDefaultSubscription() + initRoutingRulesets(context) + migrateServerListToSubscriptions() + migrateHysteria2PinSHA256() + } + /** * Initialize routing rulesets. * @param context The application context. */ - fun initRoutingRulesets(context: Context) { + private fun initRoutingRulesets(context: Context) { val exist = MmkvManager.decodeRoutingRulesets() if (exist.isNullOrEmpty()) { val rulesetList = getPresetRoutingRulesets(context) @@ -222,14 +234,10 @@ object SettingsManager { if (remarks.isNullOrEmpty()) { return null } - val serverList = decodeServerList() - for (guid in serverList) { - val profile = decodeServerConfig(guid) - if (profile != null && profile.remarks == remarks) { - return profile - } - } - return null + val serverList = decodeAllServerList() + return serverList + .mapNotNull { guid -> decodeServerConfig(guid) } + .firstOrNull { it.remarks == remarks } } /** @@ -400,7 +408,7 @@ object SettingsManager { /** * Ensure default settings are present in MMKV. */ - fun ensureDefaultSettings() { + private fun ensureDefaultSettings() { // Write defaults in the exact order requested by the user ensureDefaultValue(AppConfig.PREF_MODE, AppConfig.VPN) ensureDefaultValue(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN) @@ -424,14 +432,14 @@ object SettingsManager { } } - fun migrateHysteria2PinSHA256() { + private fun migrateHysteria2PinSHA256() { // Check if migration has already been done val migrationKey = "hysteria2_pin_sha256_migrated" if (MmkvManager.decodeSettingsBool(migrationKey, false)) { return } - val serverList = decodeServerList() + val serverList = decodeAllServerList() for (guid in serverList) { val profile = decodeServerConfig(guid) ?: continue @@ -449,4 +457,81 @@ object SettingsManager { MmkvManager.encodeSettings(migrationKey, true) } + /** + * Migrates server list from legacy KEY_ANG_CONFIGS to subscription-based storage. + * This method should be called once during app initialization after the storage structure change. + * Servers are grouped by their subscriptionId into respective subscription's serverList. + * Servers without subscription are moved to the default subscription. + * After migration, KEY_ANG_CONFIGS is removed. + */ + private fun migrateServerListToSubscriptions() { + // Check if migration has already been done + val migrationKey = "server_list_to_subscriptions_migrated" + if (MmkvManager.decodeSettingsBool(migrationKey, false)) { + return + } + + // Ensure default subscription exists before migration + ensureDefaultSubscription() + + // Read existing server list from legacy KEY_ANG_CONFIGS + val oldJson = MmkvManager.readLegacyServerList() + if (oldJson.isNullOrBlank()) { + // No data to migrate, mark as done + MmkvManager.encodeSettings(migrationKey, true) + return + } + + val guids = JsonUtil.fromJson(oldJson, Array::class.java) ?: run { + MmkvManager.encodeSettings(migrationKey, true) + return + } + + val subscriptionServerMap = mutableMapOf>() + + // Group servers by subscription (use default subscription for empty subscriptionId) + guids.forEach { guid -> + val config = decodeServerConfig(guid) ?: return@forEach + val subId = config.subscriptionId.ifEmpty { DEFAULT_SUBSCRIPTION_ID } + + subscriptionServerMap.getOrPut(subId) { mutableListOf() }.add(guid) + } + + // Update each subscription's serverList (including default subscription) + subscriptionServerMap.forEach { (subId, serverGuids) -> + val subItem = decodeSubscription(subId) + if (subItem != null) { + subItem.serverList = serverGuids + encodeSubscription(subId, subItem) + } + } + + // Remove legacy KEY_ANG_CONFIGS data + // MmkvManager.removeLegacyServerListKey() + + // Mark migration as complete + MmkvManager.encodeSettings(migrationKey, true) + } + + /** + * Ensures the default subscription exists for ungrouped servers. + * This subscription is used internally to store servers without a subscription. + * Made public for migration in SettingsManager. + */ + private fun ensureDefaultSubscription() { + if (decodeSubscription(DEFAULT_SUBSCRIPTION_ID) == null) { + val defaultSub = SubscriptionItem( + remarks = "Default", + serverList = mutableListOf() + ) + encodeSubscription(DEFAULT_SUBSCRIPTION_ID, defaultSub) + + // Move top + val subsList = decodeSubsList() + if (subsList.count() > 1) { + swapSubscriptions(0, subsList.count() - 1) + } + } + } + } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt index 2f9b9a54..e2c7c7fb 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt @@ -6,8 +6,6 @@ import android.util.Log import com.google.gson.JsonArray import com.v2ray.ang.AppConfig import com.v2ray.ang.dto.ConfigResult -import com.v2ray.ang.enums.EConfigType -import com.v2ray.ang.enums.NetworkType import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.V2rayConfig @@ -15,6 +13,8 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean +import com.v2ray.ang.enums.EConfigType +import com.v2ray.ang.enums.NetworkType import com.v2ray.ang.extension.isNotNullEmpty import com.v2ray.ang.extension.nullIfBlank import com.v2ray.ang.fmt.HttpFmt @@ -139,7 +139,7 @@ object V2rayConfigManager { private fun getV2rayGroupConfig(context: Context, guid: String, config: ProfileItem): ConfigResult { val result = ConfigResult(false) - val serverList = MmkvManager.decodeServerList() + val serverList = MmkvManager.decodeAllServerList() val configList = serverList .mapNotNull { id -> MmkvManager.decodeServerConfig(id) } .filter { profile -> diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt index 4f29b5d4..4ca88424 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt @@ -18,6 +18,7 @@ import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastSuccess import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsChangeManager +import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.WebDavManager import com.v2ray.ang.util.ZipUtil import kotlinx.coroutines.Dispatchers @@ -124,6 +125,8 @@ class BackupActivity : HelperBaseActivity() { val count = MMKV.restoreAllFromDirectory(backupDir) SettingsChangeManager.makeSetupGroupTab() SettingsChangeManager.makeRestartService() + + SettingsManager.initApp(this) return count > 0 } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt index 988c3746..e7023721 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt @@ -164,6 +164,7 @@ class GroupServerFragment : BaseFragment() { val intent = Intent().putExtra("guid", guid) .putExtra("isRunning", mainViewModel.isRunning.value) .putExtra("createConfigType", profile.configType.value) + .putExtra("subscriptionId", subId) when (profile.configType) { EConfigType.CUSTOM -> { ownerActivity.startActivity(intent.setClass(ownerActivity, ServerCustomConfigActivity::class.java)) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt index 8169f199..5c7db83e 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt @@ -145,7 +145,7 @@ class SubEditActivity : BaseActivity() { del_config = menu.findItem(R.id.del_config) save_config = menu.findItem(R.id.save_config) - if (editSubId.isEmpty()) { + if (editSubId.isEmpty() || editSubId == AppConfig.DEFAULT_SUBSCRIPTION_ID) { del_config?.isVisible = false } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt index 0c482499..164fba6a 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt @@ -5,7 +5,9 @@ import android.text.TextUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import com.v2ray.ang.AppConfig import com.v2ray.ang.contracts.BaseAdapterListener import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding import com.v2ray.ang.helper.ItemTouchHelperAdapter @@ -37,6 +39,7 @@ class SubSettingRecyclerAdapter( holder.itemSubSettingBinding.layoutRemove.setOnClickListener { adapterListener?.onRemove(subId, position) } + holder.itemSubSettingBinding.layoutRemove.isVisible = subId != AppConfig.DEFAULT_SUBSCRIPTION_ID holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked -> if (!it.isPressed) return@setOnCheckedChangeListener diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TaskerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TaskerActivity.kt index 89f4fbe1..41f5430b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TaskerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/TaskerActivity.kt @@ -30,7 +30,7 @@ class TaskerActivity : BaseActivity() { lstData.add("Default") lstGuid.add(AppConfig.TASKER_DEFAULT_GUID) - MmkvManager.decodeServerList().forEach { key -> + MmkvManager.decodeAllServerList().forEach { key -> MmkvManager.decodeServerConfig(key)?.let { config -> lstData.add(config.remarks) lstGuid.add(key) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index 0b0de831..668c5aef 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -35,7 +35,7 @@ import kotlinx.coroutines.withContext import java.util.Collections class MainViewModel(application: Application) : AndroidViewModel(application) { - private var serverList = MmkvManager.decodeServerList() + private var serverList = mutableListOf() // MmkvManager.decodeServerList() var subscriptionId: String = MmkvManager.decodeSettingsString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty() //var keywordFilter: String = MmkvManager.MmkvManager.decodeSettingsString(AppConfig.CACHE_KEYWORD_FILTER, "")?:"" @@ -69,10 +69,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } /** - * Reloads the server list. + * Reloads the server list based on current subscription filter. */ fun reloadServerList() { - serverList = MmkvManager.decodeServerList() + serverList = if (subscriptionId.isEmpty()) { + MmkvManager.decodeAllServerList() + } else { + MmkvManager.decodeServerList(subscriptionId) + } + updateCache() updateListAction.value = -1 } @@ -129,14 +134,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { */ fun swapServer(fromPosition: Int, toPosition: Int) { if (subscriptionId.isEmpty()) { - Collections.swap(serverList, fromPosition, toPosition) - } else { - val fromPosition2 = serverList.indexOf(serversCache[fromPosition].guid) - val toPosition2 = serverList.indexOf(serversCache[toPosition].guid) - Collections.swap(serverList, fromPosition2, toPosition2) + return } + + Collections.swap(serverList, fromPosition, toPosition) Collections.swap(serversCache, fromPosition, toPosition) - MmkvManager.encodeServerList(serverList) + + MmkvManager.encodeServerList(serverList, subscriptionId) } /** @@ -160,9 +164,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { // MmkvManager.encodeServerConfig(guid, config) // } - if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) { - continue - } +// if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) { +// continue +// } if (keywordFilter.isEmpty() || profile.remarks.lowercase().contains(keywordFilter.lowercase())) { serversCache.add(ServersCache(guid, profile)) @@ -279,14 +283,21 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } val groups = mutableListOf() - groups.add( - GroupMapItem( - id = "", - remarks = context.getString(R.string.filter_config_all) + if (subscriptions.count() > 1) { + groups.add( + GroupMapItem( + id = "", + remarks = context.getString(R.string.filter_config_all) + ) ) - ) + } subscriptions.forEach { sub -> - groups.add(GroupMapItem(id = sub.guid, remarks = sub.subscription.remarks)) + groups.add( + GroupMapItem( + id = sub.guid, + remarks = sub.subscription.remarks + ) + ) } return groups } @@ -371,8 +382,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { data class ServerDelay(var guid: String, var testDelayMillis: Long) val serverDelays = mutableListOf() - val serverList = MmkvManager.decodeServerList() - serverList.forEach { key -> + // Use current subscription filter + val serverListToSort = MmkvManager.decodeServerList(subscriptionId) + serverListToSort.forEach { key -> val delay = MmkvManager.decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay)) } @@ -383,7 +395,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { serverList.add(it.guid) } - MmkvManager.encodeServerList(serverList) + // Save to appropriate list based on subscriptionId + MmkvManager.encodeServerList(serverList, subscriptionId) } /**