From 2231c38792cbf85ce3b7c160adb8b80a6dffa719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=89=E7=A7=8B?= <497055328@qq.com> Date: Tue, 15 Sep 2026 16:21:44 +0800 Subject: [PATCH] fix: load recent photo prices independently of photo results --- .../yzx/kiosk/ui/face/RecentPhotosFlowTest.kt | 59 ++++++++++++++++++- .../FaceRecognitionResultViewModel.kt | 9 ++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/yzx/kiosk/ui/face/RecentPhotosFlowTest.kt b/app/src/androidTest/java/com/yzx/kiosk/ui/face/RecentPhotosFlowTest.kt index b4d5e56..18fe3fa 100644 --- a/app/src/androidTest/java/com/yzx/kiosk/ui/face/RecentPhotosFlowTest.kt +++ b/app/src/androidTest/java/com/yzx/kiosk/ui/face/RecentPhotosFlowTest.kt @@ -102,7 +102,7 @@ class RecentPhotosFlowTest { req.body!!.writeTo(buffer) val ids = Gson().fromJson(buffer.readUtf8(), com.google.gson.JsonObject::class.java).getAsJsonArray("image_id") val amount = "${ids.size() * 2}.00" - val extra = electronicAmount?.let { ",\"price_electronic\":\"$it\",\"amount_electronic\":\"$it\"" }.orEmpty() + val extra = electronicAmount?.let { ",\"price_electronic\":\"$it\",\"amount_electronic\":\"${it.toBigDecimal().multiply(ids.size().toBigDecimal()).toPlainString()}\"" }.orEmpty() """{"code":100000,"data":{"price_image":"2.00","amount":"$amount"$extra}}""" } req.url.encodedPath.endsWith("get-pay-url") && payFails -> @@ -467,7 +467,10 @@ class RecentPhotosFlowTest { compose.onNodeWithText("二维码加载失败").assertIsDisplayed() qrFails = false compose.onNodeWithText("重新加载").performClick() - compose.waitUntil(10_000) { completionVm!!.qrCodeUrl.value.isNotEmpty() } + // URL receipt precedes the background QR bitmap generation. + compose.waitUntil(10_000) { + compose.onAllNodesWithContentDescription("电子版照片下载二维码").fetchSemanticsNodes().isNotEmpty() + } compose.onNodeWithContentDescription("电子版照片下载二维码").assertIsDisplayed() compose.onNodeWithText("请在屏幕下方拿取照片").assertDoesNotExist() screenshot("electronic-completion") @@ -479,12 +482,62 @@ class RecentPhotosFlowTest { assertEquals(papersBefore, app.appStoreDataSource.getRemainingPaperNum()) } + @Test fun emptyRecentPhotosStillShowConfiguredUnitPrices() { + electronicAmount = "1.00" + compose.setContent { FaceRecognitionResultScreen(source = AppRoutes.RECENT_RESULT_SOURCE, viewModel = resultVm) } + compose.waitUntil(10_000) { resultVm.recentPhotosState.value == RecentPhotosState.EMPTY } + compose.waitForIdle() + assertTrue("Entering recent photos must request prices even when the photo list is empty", + requests.any { it.url.encodedPath.endsWith("verify-result") }) + compose.waitUntil(10_000) { !resultVm.quoteLoading.value } + compose.onNodeWithText("打印版 2元/张").assertIsDisplayed() + compose.onNodeWithText("电子版 1元/张").assertIsDisplayed() + compose.onNodeWithText("购买打印照片\n0元").assertIsNotEnabled() + compose.onNodeWithText("购买电子照片\n0元").assertIsNotEnabled() + } + + @Test fun recentPhotosSelectionUpdatesBothQuotesWithoutReloadReset() { + electronicAmount = "1.00" + val bitmap = Bitmap.createBitmap(160, 100, Bitmap.Config.ARGB_8888).apply { eraseColor(Color.BLUE) } + val photos = (1..2).map { id -> + val file = File(context.cacheDir, "recent-price-$id.png") + file.outputStream().use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } + val url = file.toURI().toString() + FaceSearchResult(id, url, url, url, url) + } + recentBody = Gson().toJson(mapOf("count" to 2, "results" to photos)) + compose.setContent { FaceRecognitionResultScreen(source = AppRoutes.RECENT_RESULT_SOURCE, viewModel = resultVm) } + compose.waitUntil(10_000) { resultVm.recentPhotosState.value == RecentPhotosState.READY && !resultVm.quoteLoading.value } + compose.onNodeWithText("打印版 2元/张").assertIsDisplayed() + compose.onNodeWithText("电子版 1元/张").assertIsDisplayed() + compose.onNodeWithText("购买电子照片\n0元").assertIsNotEnabled() + assertEquals(1, requests.count { it.url.encodedPath.endsWith("verify-result") }) + compose.runOnIdle { resultVm.toggleSelectAll() } + compose.waitUntil(10_000) { !resultVm.quoteLoading.value && resultVm.electronicTotal.value == "2.00" } + compose.onNodeWithText("购买打印照片\n4元").assertIsEnabled() + compose.onNodeWithText("购买电子照片\n2元").assertIsEnabled() + compose.runOnIdle { resultVm.loadRecentPhotos() } + compose.waitForIdle() + assertEquals("4.00", resultVm.totalPrice.value) + assertEquals("2.00", resultVm.electronicTotal.value) + assertEquals(2, requests.count { it.url.encodedPath.endsWith("verify-result") }) + compose.runOnIdle { resultVm.toggleSelectAll() } + compose.waitUntil(10_000) { !resultVm.quoteLoading.value && resultVm.totalPrice.value == "0.00" } + compose.onNodeWithText("打印版 2元/张").assertIsDisplayed() + compose.onNodeWithText("电子版 1元/张").assertIsDisplayed() + compose.onNodeWithText("购买电子照片\n0元").assertIsNotEnabled() + } + @Test fun errorCanRetryIntoEmptyState() { + electronicAmount = "1.00" recentCode = 403 compose.setContent { FaceRecognitionResultScreen(source = AppRoutes.RECENT_RESULT_SOURCE, viewModel = resultVm) } compose.waitUntil(10_000) { resultVm.recentPhotosState.value == RecentPhotosState.ERROR } compose.onNodeWithText("照片加载失败,请重试").assertIsDisplayed() - compose.onNodeWithText("购买打印照片\n--元").assertIsNotEnabled() + compose.waitUntil(10_000) { !resultVm.quoteLoading.value } + compose.onNodeWithText("打印版 2元/张").assertIsDisplayed() + compose.onNodeWithText("电子版 1元/张").assertIsDisplayed() + compose.onNodeWithText("购买打印照片\n0元").assertIsNotEnabled() recentCode = 200 compose.onNodeWithText("重试").performClick() compose.waitUntil(10_000) { resultVm.recentPhotosState.value == RecentPhotosState.EMPTY } diff --git a/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt index e529d34..5cb2919 100644 --- a/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt +++ b/app/src/main/java/com/yzx/kiosk/ui/face/viewmodel/FaceRecognitionResultViewModel.kt @@ -74,7 +74,11 @@ class FaceRecognitionResultViewModel @Inject constructor( toPage(AppRoutes.buildRecentPhotosRoute()) } - fun loadRecentPhotos() = recentLoader.load() + fun loadRecentPhotos() { + // Pricing belongs to the page, not to a successful/non-empty photo response. + if (recentPhotosState.value == RecentPhotosState.IDLE) verifyResult(emptyList()) + recentLoader.load() + } fun retryRecentPhotos() = recentLoader.load(retry = true) // 原始结果列表(用于获取图片id) @@ -177,6 +181,7 @@ class FaceRecognitionResultViewModel @Inject constructor( // Returning from a pushed recent-photos page must retain selection, pricing and image state. if (faceResultsInitialized) return faceResultsInitialized = true + verifyResult(emptyList()) viewModelScope.launch { applyPhotoList(results) } } @@ -215,8 +220,6 @@ class FaceRecognitionResultViewModel @Inject constructor( // 初始化加载状态 _imageLoadStates.value = initialPhotos.associate { it.url to ImageLoadState() } - // 页面初始化时调用验证接口(空列表,type: 2),获取默认价格 - verifyResult(emptyList()) LogUtils.d(TAG, "图片列表初始化完成,共 ${initialPhotos.size} 张,宽高比将在图片加载成功后逐张更新") }