From 1fd05e16b5f08fe3156e613ed11fc6b072099f89 Mon Sep 17 00:00:00 2001 From: eliotcougar Date: Thu, 10 Sep 2026 06:50:59 +0300 Subject: [PATCH] Keep built-in asset row identities stable across reloads (#6199) Use a builtin: filename-based ID instead of generating a fresh UUID each time the asset list is rebuilt. This lets the existing GUID-keyed list and file-metadata map recognize the same built-in asset after reloads and download-source changes. Extract the fix and its two JVM regression tests from local/talkback-all-10. Preserve stored asset GUIDs, matching built-in replacement, locked flags, and URL behavior. No persisted IDs, accessibility semantics, metadata formatting, or editor behavior are changed. --- .../ang/ui/userasset/UserAssetViewModel.kt | 6 +-- .../ui/userasset/UserAssetViewModelTest.kt | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 V2rayNG/app/src/test/java/com/v2ray/ang/ui/userasset/UserAssetViewModelTest.kt diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetViewModel.kt index 89c4e507..81716769 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetViewModel.kt @@ -11,7 +11,6 @@ import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.ui.base.BaseViewModel import com.v2ray.ang.util.HttpUtil import com.v2ray.ang.util.LogUtil -import com.v2ray.ang.util.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive @@ -50,7 +49,7 @@ class UserAssetViewModel(application: Application) : BaseViewModel(application) }.also { reloadJob = it } } - private fun buildAssetList( + internal fun buildAssetList( decodedAssets: List?, geoFilesSource: String ): List { @@ -59,7 +58,8 @@ class UserAssetViewModel(application: Application) : BaseViewModel(application) .filter { geoFile -> savedAssets.none { it.assetUrl.remarks == geoFile } } .map { AssetUrlCache( - Utils.getUuid(), + // Built-in rows have no persisted GUID; keep their UI identity across reloads. + "builtin:$it", AssetUrlItem( it, String.format(AppConfig.GITHUB_DOWNLOAD_URL, geoFilesSource).concatUrl(it), diff --git a/V2rayNG/app/src/test/java/com/v2ray/ang/ui/userasset/UserAssetViewModelTest.kt b/V2rayNG/app/src/test/java/com/v2ray/ang/ui/userasset/UserAssetViewModelTest.kt new file mode 100644 index 00000000..d3ad2893 --- /dev/null +++ b/V2rayNG/app/src/test/java/com/v2ray/ang/ui/userasset/UserAssetViewModelTest.kt @@ -0,0 +1,45 @@ +package com.v2ray.ang.ui.userasset + +import android.app.Application +import com.v2ray.ang.AppConfig +import com.v2ray.ang.dto.entities.AssetUrlCache +import com.v2ray.ang.dto.entities.AssetUrlItem +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.mockito.Mockito.mock + +class UserAssetViewModelTest { + private val viewModel = UserAssetViewModel(mock(Application::class.java)) + + @Test + fun builtInIdentitySurvivesReloadAndSourceChange() { + val first = viewModel.buildAssetList(null, "first/source") + val reloaded = viewModel.buildAssetList(emptyList(), "second/source") + + assertEquals(3, first.size) + assertEquals(first.size, first.map { it.guid }.distinct().size) + assertEquals(first.map { it.guid }, reloaded.map { it.guid }) + assertTrue(reloaded.all { it.assetUrl.locked == true }) + assertEquals( + AppConfig.GEOIP_ONLY_CN_PRIVATE_URL, + reloaded.single { it.assetUrl.remarks == AppConfig.GEOIP_ONLY_CN_PRIVATE_DAT }.assetUrl.url, + ) + } + + @Test + fun savedAssetKeepsItsIdentityAndReplacesMatchingBuiltIn() { + val saved = AssetUrlCache("saved-guid", AssetUrlItem(AppConfig.GEOSITE_DAT, "https://example.invalid/geosite.dat")) + val custom = AssetUrlCache("custom-guid", AssetUrlItem("custom.dat", "file")) + val builtIns = viewModel.buildAssetList(emptyList(), "source") + val rows = viewModel.buildAssetList(listOf(saved, custom), "source") + + assertEquals(4, rows.size) + assertEquals(saved, rows.single { it.assetUrl.remarks == saved.assetUrl.remarks }) + assertEquals(custom, rows.single { it.guid == custom.guid }) + assertEquals( + builtIns.filter { it.assetUrl.remarks != AppConfig.GEOSITE_DAT }.map { it.guid }, + rows.filter { it.assetUrl.locked == true }.map { it.guid }, + ) + } +}