diff --git a/core/build.gradle.kts b/core/build.gradle.kts index aa3b9d9..fd3c00b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -12,6 +12,21 @@ android { defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() consumerProguardFiles("proguard-rules.pro") + + // 构建期扫描 assets/animations/ 生成 WebP 文件列表,避免运行期硬编码 (1..152) + // 与 assets/ 目录耦合却不校验,导致增删文件后隐性 bug + val webpFiles = layout.projectDirectory + .dir("src/main/assets/animations") + .asFile + .listFiles { f -> f.extension.equals("webp", ignoreCase = true) } + ?.map { it.name } + ?.sorted() + ?: emptyList() + require(webpFiles.isNotEmpty()) { "assets/animations/ 不应为空,请检查目录" } + // 拼成 Java 数组字面量: new String[]{"001.webp","002.webp",...} + val quoted = webpFiles.joinToString(",") { name -> "\"$name\"" } + val arrayLiteral = "new String[]{$quoted}" + buildConfigField("String[]", "WEBP_FILES", arrayLiteral) } buildTypes { diff --git a/core/src/main/kotlin/plus/rua/project/ui/AnimatedWebp.kt b/core/src/main/kotlin/plus/rua/project/ui/AnimatedWebp.kt index efae534..b641955 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/AnimatedWebp.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/AnimatedWebp.kt @@ -15,11 +15,15 @@ import com.github.panpf.sketch.rememberAsyncImageState import com.github.panpf.sketch.request.ImageOptions import com.github.panpf.sketch.request.repeatCount import plus.rua.project.getWebpUri +import plus.rua.project.shared.BuildConfig /** - * WebP 动画文件名列表(001.webp ~ 152.webp)。 + * WebP 动画文件名列表,由构建期 `BuildConfig.WEBP_FILES` 注入 + * (见 `core/build.gradle.kts` 扫描 `assets/animations/` 的 `buildConfigField`)。 + * + * `internal` 让同模块测试可直接访问(见 `AnimatedWebpFilesTest`)。 */ -private val WEBP_FILES = (1..152).map { "${it.toString().padStart(3, '0')}.webp" } +internal val WEBP_FILES: List = BuildConfig.WEBP_FILES.toList() /** * 显示动画 WebP 图片,切换日期时随机选择一个。 diff --git a/core/src/test/kotlin/plus/rua/project/ui/AnimatedWebpFilesTest.kt b/core/src/test/kotlin/plus/rua/project/ui/AnimatedWebpFilesTest.kt new file mode 100644 index 0000000..2766739 --- /dev/null +++ b/core/src/test/kotlin/plus/rua/project/ui/AnimatedWebpFilesTest.kt @@ -0,0 +1,49 @@ +package plus.rua.project.ui + +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.Test + +/** + * 守卫:运行期使用的 WebP 文件列表必须与 assets/animations/ 目录实际内容一致。 + * + * 防止两类回归: + * 1. 有人把 BuildConfig 方案回退成硬编码 (1..152),与目录脱钩后增删文件即隐性 bug + * 2. assets/animations/ 在 CI / 打包流程中意外丢失或被 Git LFS 过滤掉 + * + * 注:当前实现里 WEBP_FILES 与目录都来自同一次构建期扫描,二者天然一致; + * 本测试的核心价值是锁定「两者一致」这个不变量,使上述回归一旦发生即立即失败。 + */ +class AnimatedWebpFilesTest { + + private val animationsDir = File("src/main/assets/animations") + + @Test + fun webpFilesMatchDirectoryContents() { + assertTrue(animationsDir.exists(), "assets/animations 目录应存在: ${animationsDir.absolutePath}") + + val onDisk = animationsDir.listFiles { f -> f.extension.equals("webp", ignoreCase = true) } + ?.map { it.name } + ?.sorted() + ?: emptyList() + assertTrue(onDisk.isNotEmpty(), "assets/animations 不应为空(若失败请检查 Git LFS / checkout 完整性)") + + // WEBP_FILES 由构建期 BuildConfig 注入(见 core/build.gradle.kts 的 buildConfigField) + val inCode = WEBP_FILES.sorted() + + assertEquals(onDisk, inCode, "WEBP_FILES 必须与 assets/animations/ 实际 webp 文件一一对应") + } + + @Test + fun webpFilesUseZeroPaddedThreeDigitNames() { + // 锁定命名约定:NNN.webp(三位零填充),防止有人误改成 1.webp / 01.webp 等 + val expected = Regex("""^\d{3}\.webp$""") + WEBP_FILES.forEach { name -> + assertTrue( + expected.matches(name), + "文件名 $name 不符合 NNN.webp 约定,getWebpUri 与 AnimatedWebp 依赖此格式", + ) + } + } +}