@@ -221,21 +221,14 @@ object AngConfigManager {
221221 if (servers == null ) {
222222 return 0
223223 }
224- val removedSelectedServer =
225- if (! TextUtils .isEmpty(subid) && ! append) {
226- MmkvManager .decodeServerConfig(
227- MmkvManager .getSelectServer().orEmpty()
228- )?.let {
229- if (it.subscriptionId == subid) {
230- return @let it
231- }
232- return @let null
233- }
234- } else {
235- null
236- }
237- if (! append) {
238- MmkvManager .removeServerViaSubid(subid)
224+ // Find the currently selected server that matches the subscription ID
225+ val removedSelected = if (subid.isNotBlank() && ! append) {
226+ MmkvManager .getSelectServer()
227+ .takeIf { it?.isNotBlank() == true }
228+ ?.let { MmkvManager .decodeServerConfig(it) }
229+ ?.takeIf { it.subscriptionId == subid }
230+ } else {
231+ null
239232 }
240233
241234 val subItem = MmkvManager .decodeSubscription(subid)
@@ -254,18 +247,12 @@ object AngConfigManager {
254247
255248 // Batch save all parsed configs (only one serverList read/write)
256249 if (configs.isNotEmpty()) {
257- val keys = batchSaveConfigs(configs, subid)
258-
259- // Handle removed selected server
260- removedSelectedServer?.let { removed ->
261- val matchKey = keys.find { key ->
262- val savedConfig = MmkvManager .decodeServerConfig(key)
263- savedConfig != null &&
264- savedConfig.server == removed.server &&
265- savedConfig.serverPort == removed.serverPort
266- }
267- matchKey?.let { MmkvManager .setSelectServer(it) }
250+ if (! append) {
251+ MmkvManager .removeServerViaSubid(subid)
268252 }
253+ val keyToProfile = batchSaveConfigs(configs, subid)
254+ val matchKey = findMatchedProfileKey(keyToProfile, removedSelected)
255+ matchKey?.let { MmkvManager .setSelectServer(it) }
269256 }
270257
271258 return configs.size
@@ -281,10 +268,10 @@ object AngConfigManager {
281268 *
282269 * @param configs The list of ProfileItem to save.
283270 * @param subid The subscription ID.
284- * @return The list of generated keys.
271+ * @return Map of generated keys to their corresponding ProfileItem .
285272 */
286- private fun batchSaveConfigs (configs : List <ProfileItem >, subid : String ): List <String > {
287- val keys = mutableListOf <String >()
273+ private fun batchSaveConfigs (configs : List <ProfileItem >, subid : String ): Map <String , ProfileItem > {
274+ val keyToProfile = mutableMapOf <String , ProfileItem >()
288275
289276 // Read serverList once
290277 val serverList = MmkvManager .decodeServerList(subid)
@@ -302,12 +289,67 @@ object AngConfigManager {
302289 needSetSelected = false
303290 }
304291 }
305- keys.add( key)
292+ keyToProfile[ key] = config
306293 }
307294
308295 // Write serverList once
309296 MmkvManager .encodeServerList(serverList, subid)
310- return keys
297+ return keyToProfile
298+ }
299+
300+ /* *
301+ * Finds a matched profile key from the given key-profile map using multi-level matching.
302+ * Matching priority (from highest to lowest):
303+ * 1. Exact match: server + port + password
304+ * 2. Match by remarks (exact match)
305+ * 3. Match by server + port
306+ * 4. Match by server only
307+ *
308+ * @param keyToProfile Map of server keys to their ProfileItem
309+ * @param target Target profile to match
310+ * @return Matched key or null
311+ */
312+ private fun findMatchedProfileKey (keyToProfile : Map <String , ProfileItem >, target : ProfileItem ? ): String? {
313+ if (keyToProfile.isEmpty() || target == null ) return null
314+
315+ // Level 1: Match by remarks
316+ if (target.remarks.isNotBlank()) {
317+ keyToProfile.entries.firstOrNull { (_, saved) ->
318+ isSameText(saved.remarks, target.remarks)
319+ }?.key?.let { return it }
320+ }
321+
322+ // Level 2: Exact match (server + port + password)
323+ keyToProfile.entries.firstOrNull { (_, saved) ->
324+ isSameText(saved.server, target.server) &&
325+ isSameText(saved.serverPort, target.serverPort) &&
326+ isSameText(saved.password, target.password)
327+ }?.key?.let { return it }
328+
329+ // Level 3: Match by server + port
330+ keyToProfile.entries.firstOrNull { (_, saved) ->
331+ isSameText(saved.server, target.server) &&
332+ isSameText(saved.serverPort, target.serverPort)
333+ }?.key?.let { return it }
334+
335+ // Level 4: Match by server only
336+ keyToProfile.entries.firstOrNull { (_, saved) ->
337+ isSameText(saved.server, target.server)
338+ }?.key?.let { return it }
339+
340+ return null
341+ }
342+
343+ /* *
344+ * Case-insensitive trimmed string comparison.
345+ *
346+ * @param left First string
347+ * @param right Second string
348+ * @return True if both are non-empty and equal (case-insensitive, trimmed)
349+ */
350+ private fun isSameText (left : String? , right : String? ): Boolean {
351+ if (left.isNullOrBlank() || right.isNullOrBlank()) return false
352+ return left.trim().equals(right.trim(), ignoreCase = true )
311353 }
312354
313355 /* *
0 commit comments