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.
This commit is contained in:
eliotcougar authored and nerpa committed 2026-09-14 16:53:36 +03:00
1 parent e7dcec1eff
commit 1fd05e16b5
2 files changed
+48 -3

No files matched your search

@@ -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<AssetUrlCache>?,
geoFilesSource: String
): List<AssetUrlCache> {
@@ -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),
@@ -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 },
)
}
}