diff --git a/app/build.gradle.kts b/app/build.gradle.kts index be7fcd1ed..8ba959c0c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -133,6 +133,7 @@ dependencies { androidTestImplementation(libs.camera.lifecycle) // to reset CameraX between tests androidTestImplementation(libs.truth) androidTestImplementation(libs.testParameterInjector) + androidTestImplementation(project(":ui:uistate")) androidTestImplementation(project(":ui:components:capture")) androidTestImplementation(project(":ui:debug")) androidTestUtil(libs.androidx.orchestrator) @@ -169,6 +170,7 @@ dependencies { coreLibraryDesugaring(libs.desugar.jdk.libs) // capture components + implementation(project(":ui:uistate")) implementation(project(":ui:components:capture")) implementation(project(":ui:debug")) diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt index c9c22c7af..9960e9c95 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt @@ -47,7 +47,7 @@ import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_HDR_BUTTON import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_1_1_BUTTON import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_BUTTON import com.google.jetpackcamera.ui.components.capture.R as CaptureR -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.VIDEO_CAPTURE_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.getResString @@ -59,7 +59,7 @@ import com.google.jetpackcamera.utils.setConcurrentCameraModeInSettings import com.google.jetpackcamera.utils.stateDescriptionMatches import com.google.jetpackcamera.utils.visitSettingsScreen import com.google.jetpackcamera.utils.waitForCaptureButton -import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithText import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -170,8 +170,7 @@ class ConcurrentCameraTest { .assert( stateDescriptionMatches( getResString( - CaptureR.string - .quick_settings_description_capture_mode_video_only + CaptureR.string.quick_settings_description_capture_mode_video_only ) ) ) @@ -228,7 +227,10 @@ class ConcurrentCameraTest { longClickForVideoRecordingCheckingElapsedTime() - waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) + waitForNodeWithText( + StateR.string.toast_video_capture_success, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) } } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt index 6649d27c5..1bc444242 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt @@ -31,9 +31,8 @@ import com.google.jetpackcamera.model.FlashMode import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON import com.google.jetpackcamera.ui.components.capture.FLIP_CAMERA_BUTTON -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG import com.google.jetpackcamera.ui.components.capture.SCREEN_FLASH_OVERLAY -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.SCREEN_FLASH_OVERLAY_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS @@ -46,6 +45,7 @@ import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.setFlashMode import com.google.jetpackcamera.utils.waitForCaptureButton import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithText import org.junit.Before import org.junit.Rule import org.junit.Test @@ -131,7 +131,10 @@ internal class FlashDeviceTest { .assertExists() .performClick() - composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, + IMAGE_CAPTURE_TIMEOUT_MILLIS + ) } @Test @@ -163,8 +166,8 @@ internal class FlashDeviceTest { SCREEN_FLASH_OVERLAY_TIMEOUT_MILLIS ) - composeTestRule.waitForNodeWithTag( - IMAGE_CAPTURE_SUCCESS_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, IMAGE_CAPTURE_TIMEOUT_MILLIS ) } @@ -195,8 +198,8 @@ internal class FlashDeviceTest { composeTestRule.setFlashMode(FlashMode.ON) composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitForNodeWithTag( - VIDEO_CAPTURE_SUCCESS_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_video_capture_success, VIDEO_CAPTURE_TIMEOUT_MILLIS ) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt index 3bda5787f..6c503f4cf 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt @@ -19,6 +19,8 @@ import android.app.Activity import android.net.Uri import android.provider.MediaStore import android.view.KeyEvent +import androidx.annotation.StringRes +import androidx.compose.ui.test.ComposeTimeoutException import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isNotDisplayed @@ -31,9 +33,7 @@ import androidx.test.uiautomator.UiDevice import com.google.common.truth.Truth import com.google.jetpackcamera.feature.postcapture.ui.VIEWER_POST_CAPTURE_IMAGE import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_FAILURE_TAG -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.CacheParam import com.google.jetpackcamera.utils.FILE_PREFIX import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS @@ -46,16 +46,17 @@ import com.google.jetpackcamera.utils.VIDEO_PREFIX import com.google.jetpackcamera.utils.deleteFilesInDirAfterTimestamp import com.google.jetpackcamera.utils.doesFileExist import com.google.jetpackcamera.utils.doesMediaExist -import com.google.jetpackcamera.utils.ensureTagNotAppears import com.google.jetpackcamera.utils.expectedNumFiles import com.google.jetpackcamera.utils.getMultipleImageCaptureIntent import com.google.jetpackcamera.utils.getSingleImageCaptureIntent import com.google.jetpackcamera.utils.getTestUri import com.google.jetpackcamera.utils.longClickForVideoRecording +import com.google.jetpackcamera.utils.onNodeWithText import com.google.jetpackcamera.utils.runMainActivityMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runMainActivityScenarioTestForResult import com.google.jetpackcamera.utils.waitForCaptureButton import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithText import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import org.junit.Rule @@ -168,8 +169,8 @@ internal class ImageCaptureDeviceTest { .assertExists() .performClick() - composeTestRule.waitForNodeWithTag( - IMAGE_CAPTURE_FAILURE_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_capture_failure, IMAGE_CAPTURE_TIMEOUT_MILLIS ) uiDevice.pressBack() @@ -193,8 +194,8 @@ internal class ImageCaptureDeviceTest { composeTestRule.longClickForVideoRecording() - composeTestRule.ensureTagNotAppears( - VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG, + ensureTextNotAppears( + StateR.string.toast_video_capture_external_unsupported, VIDEO_CAPTURE_TIMEOUT_MILLIS ) @@ -225,7 +226,7 @@ internal class ImageCaptureDeviceTest { repeat(2) { clickCaptureAndWaitUntilMessageDisappears( IMAGE_CAPTURE_TIMEOUT_MILLIS, - IMAGE_CAPTURE_SUCCESS_TAG + StateR.string.toast_image_capture_success ) } clickCapture() @@ -253,7 +254,7 @@ internal class ImageCaptureDeviceTest { repeat(2) { clickCaptureAndWaitUntilMessageDisappears( IMAGE_CAPTURE_TIMEOUT_MILLIS, - IMAGE_CAPTURE_SUCCESS_TAG + StateR.string.toast_image_capture_success ) } uiDevice.pressBack() @@ -298,7 +299,7 @@ internal class ImageCaptureDeviceTest { composeTestRule.waitForCaptureButton() clickCaptureAndWaitUntilMessageDisappears( IMAGE_CAPTURE_TIMEOUT_MILLIS, - IMAGE_CAPTURE_FAILURE_TAG + StateR.string.toast_capture_failure ) clickCapture() } @@ -309,10 +310,13 @@ internal class ImageCaptureDeviceTest { deleteFilesInDirAfterTimestamp(PICTURES_DIR_PATH, instrumentation, timeStamp) } - private fun clickCaptureAndWaitUntilMessageDisappears(msgTimeOut: Long, msgTag: String) { + private fun clickCaptureAndWaitUntilMessageDisappears( + msgTimeOut: Long, + @StringRes msgResId: Int + ) { clickCapture() composeTestRule.waitUntil(timeoutMillis = msgTimeOut) { - composeTestRule.onNodeWithTag(testTag = msgTag, useUnmergedTree = true).isDisplayed() + composeTestRule.onNodeWithText(msgResId).isDisplayed() } val dismissButtonMatcher = hasContentDescription(value = "dismiss", substring = true, ignoreCase = true) @@ -322,8 +326,20 @@ internal class ImageCaptureDeviceTest { composeTestRule.onNode(dismissButtonMatcher, useUnmergedTree = true) .performClick() composeTestRule.waitUntil(timeoutMillis = MESSAGE_DISAPPEAR_TIMEOUT_MILLIS) { - val node = composeTestRule.onNodeWithTag(testTag = msgTag, useUnmergedTree = true) - node.isNotDisplayed() + composeTestRule.onNodeWithText(msgResId).isNotDisplayed() + } + } + + private fun ensureTextNotAppears(@StringRes textResId: Int, timeoutMillis: Long) { + try { + composeTestRule.waitUntil(timeoutMillis = timeoutMillis) { + composeTestRule.onNodeWithText(textResId).isDisplayed() + } + throw AssertionError( + "${com.google.jetpackcamera.utils.getResString(textResId)} should not be present" + ) + } catch (e: ComposeTimeoutException) { + /* Do nothing. we want to time out here. */ } } @@ -336,8 +352,8 @@ internal class ImageCaptureDeviceTest { private fun verifyImageCaptureSuccess() { when (cacheParam) { CacheParam.NO_CACHE -> { - composeTestRule.waitForNodeWithTag( - IMAGE_CAPTURE_SUCCESS_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, IMAGE_CAPTURE_TIMEOUT_MILLIS ) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt index 297eb3ca7..92fcea40e 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt @@ -30,9 +30,8 @@ import com.google.jetpackcamera.permissions.ui.RECORD_AUDIO_PERMISSION_BUTTON import com.google.jetpackcamera.permissions.ui.REQUEST_PERMISSION_BUTTON import com.google.jetpackcamera.permissions.ui.WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_FAILURE_TAG -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG import com.google.jetpackcamera.ui.components.capture.IMAGE_WELL_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS @@ -261,8 +260,8 @@ class PermissionsTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON) .assertExists() .performClick() - composeTestRule.waitForNodeWithTag( - IMAGE_CAPTURE_SUCCESS_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS ) } @@ -299,8 +298,8 @@ class PermissionsTest { // check for image capture failure composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitForNodeWithTag( - IMAGE_CAPTURE_FAILURE_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_capture_failure, timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS ) // imageWell shouldn't appear diff --git a/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt index 5c0c8ec05..968bcbc74 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt @@ -23,17 +23,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.google.common.truth.Truth.assertThat +import com.google.jetpackcamera.feature.postcapture.R as PostCaptureR import com.google.jetpackcamera.feature.postcapture.ui.BUTTON_POST_CAPTURE_DELETE import com.google.jetpackcamera.feature.postcapture.ui.BUTTON_POST_CAPTURE_EXIT import com.google.jetpackcamera.feature.postcapture.ui.BUTTON_POST_CAPTURE_SAVE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS import com.google.jetpackcamera.feature.postcapture.ui.VIEWER_POST_CAPTURE_IMAGE import com.google.jetpackcamera.feature.postcapture.ui.VIEWER_POST_CAPTURE_VIDEO import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG import com.google.jetpackcamera.ui.components.capture.IMAGE_WELL_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.IMAGE_WELL_LOAD_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.JCA_MEDIA_DIR_PATH @@ -50,6 +48,7 @@ import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.wait import com.google.jetpackcamera.utils.waitForCaptureButton import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithText import org.junit.After import org.junit.Before import org.junit.Rule @@ -166,8 +165,8 @@ class PostCaptureTest { composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).performClick() // Wait for image save success message - composeTestRule.waitForNodeWithTag( - SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS, + composeTestRule.waitForNodeWithText( + PostCaptureR.string.snackbar_save_image_success, SAVE_MEDIA_TIMEOUT_MILLIS ) assertThat(newImageMediaExists()).isTrue() @@ -194,8 +193,8 @@ class PostCaptureTest { composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).performClick() // Wait for video save success message - composeTestRule.waitForNodeWithTag( - SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS, + composeTestRule.waitForNodeWithText( + PostCaptureR.string.snackbar_save_video_success, SAVE_MEDIA_TIMEOUT_MILLIS ) @@ -210,7 +209,10 @@ class PostCaptureTest { composeTestRule.waitForCaptureButton() assertThat(newImageMediaExists()).isFalse() composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, + IMAGE_CAPTURE_TIMEOUT_MILLIS + ) assertThat(newImageMediaExists()).isTrue() // enter postcapture via imagewell and delete recent capture enterImageWellAndDelete(VIEWER_POST_CAPTURE_IMAGE) @@ -227,7 +229,10 @@ class PostCaptureTest { assertThat(newVideoMediaExists()).isFalse() composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) + composeTestRule.waitForNodeWithText( + StateR.string.toast_video_capture_success, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) assertThat(newVideoMediaExists()).isTrue() // enter postcapture via imagewell and delete recent capture enterImageWellAndDelete(VIEWER_POST_CAPTURE_VIDEO) @@ -242,14 +247,17 @@ class PostCaptureTest { composeTestRule.waitForCaptureButton() assertThat(newImageMediaExists()).isFalse() composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) + composeTestRule.waitForNodeWithText( + StateR.string.toast_image_capture_success, + IMAGE_CAPTURE_TIMEOUT_MILLIS + ) assertThat(newImageMediaExists()).isTrue() // enter postcapture via imagewell and save recent capture val newTimestamp = System.currentTimeMillis() enterImageWellAndSave(VIEWER_POST_CAPTURE_IMAGE) - composeTestRule.waitForNodeWithTag( - SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS, + composeTestRule.waitForNodeWithText( + PostCaptureR.string.snackbar_save_image_success, SAVE_MEDIA_TIMEOUT_MILLIS ) composeTestRule.waitUntil(timeoutMillis = SAVE_MEDIA_TIMEOUT_MILLIS) { @@ -264,13 +272,16 @@ class PostCaptureTest { assertThat(newVideoMediaExists()).isFalse() composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) + composeTestRule.waitForNodeWithText( + StateR.string.toast_video_capture_success, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) assertThat(newVideoMediaExists()).isTrue() // enter postcapture via imagewell and save recent capture val newTimestamp = System.currentTimeMillis() enterImageWellAndSave(VIEWER_POST_CAPTURE_VIDEO) - composeTestRule.waitForNodeWithTag( - SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS, + composeTestRule.waitForNodeWithText( + PostCaptureR.string.snackbar_save_video_success, SAVE_MEDIA_TIMEOUT_MILLIS ) composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { diff --git a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt index 37b553c19..0ad9deddc 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt @@ -27,8 +27,7 @@ import androidx.test.uiautomator.UiDevice import com.google.common.truth.Truth import com.google.jetpackcamera.feature.postcapture.ui.VIEWER_POST_CAPTURE_VIDEO import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_FAILURE_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateR import com.google.jetpackcamera.utils.CacheParam import com.google.jetpackcamera.utils.IMAGE_PREFIX import com.google.jetpackcamera.utils.MOVIES_DIR_PATH @@ -48,6 +47,7 @@ import com.google.jetpackcamera.utils.runMainActivityScenarioTestForResult import com.google.jetpackcamera.utils.tapStartLockedVideoRecording import com.google.jetpackcamera.utils.waitForCaptureButton import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithText import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import org.junit.Rule @@ -158,8 +158,8 @@ internal class VideoRecordingDeviceTest { // Wait for the capture button to be displayed composeTestRule.waitForCaptureButton() composeTestRule.longClickForVideoRecording() - composeTestRule.waitForNodeWithTag( - VIDEO_CAPTURE_FAILURE_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_video_capture_failure, VIDEO_CAPTURE_TIMEOUT_MILLIS ) uiDevice.pressBack() @@ -171,8 +171,8 @@ internal class VideoRecordingDeviceTest { private fun verifyVideoCaptureSuccess() { when (cacheParam) { CacheParam.NO_CACHE -> { - composeTestRule.waitForNodeWithTag( - VIDEO_CAPTURE_SUCCESS_TAG, + composeTestRule.waitForNodeWithText( + StateR.string.toast_video_capture_success, VIDEO_CAPTURE_TIMEOUT_MILLIS ) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt b/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt index 9050422e8..de17d09f1 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt @@ -71,7 +71,6 @@ import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_HDR_BUTTON import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_SCROLL_CONTAINER import com.google.jetpackcamera.ui.components.capture.R as CaptureR import com.google.jetpackcamera.ui.components.capture.SETTINGS_BUTTON -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_FAILURE_TAG import org.junit.AssumptionViolatedException /** @@ -178,7 +177,9 @@ private fun ComposeTestRule.idleForVideoDuration( durationMillis: Long = VIDEO_DURATION_MILLIS, earlyExitPredicate: () -> Boolean = { // If the video capture fails, there is no point to continue the recording, so stop idling - onNodeWithTag(VIDEO_CAPTURE_FAILURE_TAG).isDisplayed() + onNodeWithText( + com.google.jetpackcamera.ui.uistateadapter.capture.R.string.toast_video_capture_failure + ).isDisplayed() } ) { // TODO: replace with a check for the timestamp UI of the video duration @@ -248,7 +249,9 @@ fun ComposeTestRule.pressAndDragToLockVideoRecording( durationMillis: Long = VIDEO_DURATION_MILLIS, checkWhileWaiting: () -> Unit = { // If the video capture fails, there is no point to continue waiting. Assert. - onNodeWithTag(VIDEO_CAPTURE_FAILURE_TAG).assertIsNotDisplayed() + onNodeWithText( + com.google.jetpackcamera.ui.uistateadapter.capture.R.string.toast_video_capture_failure + ).assertIsNotDisplayed() } ) { onNodeWithTag(CAPTURE_BUTTON).assertExists().performTouchInput { @@ -281,9 +284,9 @@ fun ComposeTestRule.longClickForVideoRecordingCheckingElapsedTime( durationMillis: Long = VIDEO_DURATION_MILLIS, checkWhileWaiting: () -> Unit = { // If the video capture fails, there is no point to continue waiting. Assert. - if (onAllNodesWithTag(VIDEO_CAPTURE_FAILURE_TAG).fetchSemanticsNodes().isNotEmpty()) { - throw AssertionError("Video capture failed!") - } + onNodeWithText( + com.google.jetpackcamera.ui.uistateadapter.capture.R.string.toast_video_capture_failure + ).assertIsNotDisplayed() } ) { onNodeWithTag(CAPTURE_BUTTON).assertExists().performTouchInput { diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/CaptureEvent.kt b/core/model/src/main/java/com/google/jetpackcamera/model/CaptureEvent.kt index 4c8172cc7..6b6b48bd1 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/CaptureEvent.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/CaptureEvent.kt @@ -120,6 +120,8 @@ sealed interface ImageCaptureEvent : CaptureEvent { override val exception: Exception, override val progress: IntProgress ) : ImageCaptureError, ProgressCaptureEvent + + data object ImageCaptureExternalUnsupported : ImageCaptureEvent } /** @@ -140,4 +142,5 @@ sealed interface VideoCaptureEvent : CaptureEvent { * @param error The [Throwable] that describes the cause of the failure. */ data class VideoCaptureError(val error: Throwable?) : VideoCaptureEvent + data object VideoCaptureExternalUnsupported : VideoCaptureEvent } diff --git a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt index f339fba3c..d60cdfc32 100644 --- a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt +++ b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt @@ -34,6 +34,7 @@ import com.google.jetpackcamera.feature.postcapture.ui.PostCaptureLayout import com.google.jetpackcamera.feature.postcapture.ui.SaveCurrentMediaButton import com.google.jetpackcamera.feature.postcapture.ui.ShareCurrentMediaButton import com.google.jetpackcamera.feature.postcapture.utils.MediaSharing +import com.google.jetpackcamera.ui.components.capture.SNACKBAR_NODE_TAG import com.google.jetpackcamera.ui.components.capture.TestableSnackbar import com.google.jetpackcamera.ui.controller.SnackBarController import com.google.jetpackcamera.ui.uistate.SnackBarUiState @@ -142,7 +143,7 @@ fun PostCaptureComponent( if (snackBarData != null) { snackBarController?.let { TestableSnackbar( - modifier = modifier.testTag(snackBarData.testTag), + modifier = modifier.testTag(SNACKBAR_NODE_TAG), snackbarToShow = snackBarData, snackbarHostState = snackbarHostState, snackBarController = snackBarController diff --git a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModel.kt b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModel.kt index f175108e7..d82122dde 100644 --- a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModel.kt +++ b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModel.kt @@ -30,12 +30,6 @@ import androidx.media3.exoplayer.ExoPlayer import com.google.jetpackcamera.data.media.Media import com.google.jetpackcamera.data.media.MediaDescriptor import com.google.jetpackcamera.data.media.MediaRepository -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_DELETE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_DELETE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS import com.google.jetpackcamera.ui.controller.SnackBarController import com.google.jetpackcamera.ui.controller.impl.SnackBarControllerImpl import com.google.jetpackcamera.ui.uistate.SnackBarUiState @@ -312,65 +306,56 @@ class PostCaptureViewModel @Inject constructor( null ) if (result != null) { - val (stringResource, testTag) = when (mediaDescriptor) { + val stringResource = when (mediaDescriptor) { is MediaDescriptor.Content.Image -> - R.string.snackbar_save_image_success to - SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS + R.string.snackbar_save_image_success is MediaDescriptor.Content.Video -> - R.string.snackbar_save_video_success to - SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS + R.string.snackbar_save_video_success } snackBarController.addSnackBarData( SnackbarData( cookie = cookie, stringResource = stringResource, - withDismissAction = true, - testTag = testTag + withDismissAction = true ) ) } else { // potential custom behavior for null Log.e(TAG, "null saved media Uri without exception") - val (stringResource, testTag) = when (mediaDescriptor) { + val stringResource = when (mediaDescriptor) { is MediaDescriptor.Content.Image -> - R.string.snackbar_save_image_failure to - SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE + R.string.snackbar_save_image_failure is MediaDescriptor.Content.Video -> - R.string.snackbar_save_video_failure to - SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE + R.string.snackbar_save_video_failure } snackBarController.addSnackBarData( SnackbarData( cookie = cookie, stringResource = stringResource, - withDismissAction = true, - testTag = testTag + withDismissAction = true ) ) } } catch (e: Exception) { // todo: custom message depending on failure reason - val (stringResource, testTag) = when (mediaDescriptor) { + val stringResource = when (mediaDescriptor) { is MediaDescriptor.Content.Image -> - R.string.snackbar_save_image_failure to - SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE + R.string.snackbar_save_image_failure is MediaDescriptor.Content.Video -> - R.string.snackbar_save_video_failure to - SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE + R.string.snackbar_save_video_failure } snackBarController.addSnackBarData( SnackbarData( cookie = cookie, stringResource = stringResource, - withDismissAction = true, - testTag = testTag + withDismissAction = true ) ) } @@ -401,14 +386,7 @@ class PostCaptureViewModel @Inject constructor( is MediaDescriptor.Content.Video -> R.string.snackbar_delete_video_failure }, - withDismissAction = true, - testTag = when (mediaDescriptor) { - is MediaDescriptor.Content.Image -> - SNACKBAR_POST_CAPTURE_IMAGE_DELETE_FAILURE - - is MediaDescriptor.Content.Video -> - SNACKBAR_POST_CAPTURE_VIDEO_DELETE_FAILURE - } + withDismissAction = true ) ) } diff --git a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/TestTags.kt b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/TestTags.kt index 62195193d..061e8584e 100644 --- a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/TestTags.kt +++ b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/TestTags.kt @@ -23,10 +23,3 @@ const val BUTTON_POST_CAPTURE_SHARE = "btn_post_capture_share" const val BUTTON_POST_CAPTURE_DELETE = "btn_post_capture_delete" const val VIEWER_POST_CAPTURE_VIDEO = "viewer_post_capture_video" const val VIEWER_POST_CAPTURE_IMAGE = "viewer_post_capture_image" - -const val SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS = "snackbar_post_capture_image_save_success" -const val SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS = "snackbar_post_capture_video_save_success" -const val SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE = "snackbar_post_capture_image_save_failure" -const val SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE = "snackbar_post_capture_video_save_failure" -const val SNACKBAR_POST_CAPTURE_IMAGE_DELETE_FAILURE = "snackbar_post_capture_image_delete_failure" -const val SNACKBAR_POST_CAPTURE_VIDEO_DELETE_FAILURE = "snackbar_post_capture_video_delete_failure" diff --git a/feature/postcapture/src/test/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModelTest.kt b/feature/postcapture/src/test/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModelTest.kt index 88f906470..5c306cb55 100644 --- a/feature/postcapture/src/test/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModelTest.kt +++ b/feature/postcapture/src/test/java/com/google/jetpackcamera/feature/postcapture/PostCaptureViewModelTest.kt @@ -24,12 +24,6 @@ import com.google.common.truth.Truth.assertThat import com.google.jetpackcamera.data.media.Media import com.google.jetpackcamera.data.media.MediaDescriptor import com.google.jetpackcamera.data.media.testing.FakeMediaRepository -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_DELETE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_DELETE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE -import com.google.jetpackcamera.feature.postcapture.ui.SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS import com.google.jetpackcamera.ui.uistate.SnackBarUiState import com.google.jetpackcamera.ui.uistate.postcapture.MediaViewerUiState import com.google.jetpackcamera.ui.uistate.postcapture.PostCaptureUiState @@ -245,8 +239,8 @@ internal class PostCaptureViewModelTest { val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_save_image_success) } @Test @@ -262,8 +256,8 @@ internal class PostCaptureViewModelTest { // Then val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_save_video_success) } @Test @@ -280,8 +274,8 @@ internal class PostCaptureViewModelTest { // Then val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_IMAGE_SAVE_FAILURE) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_save_image_failure) } @Test @@ -298,8 +292,8 @@ internal class PostCaptureViewModelTest { // Then val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_VIDEO_SAVE_FAILURE) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_save_video_failure) } @Test @@ -316,8 +310,8 @@ internal class PostCaptureViewModelTest { // Then val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_IMAGE_DELETE_FAILURE) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_delete_image_failure) } @Test @@ -334,8 +328,8 @@ internal class PostCaptureViewModelTest { // Then val snackBarUiState = viewModel.snackBarUiState.value.asEnabled() assertThat(snackBarUiState.snackBarQueue).hasSize(1) - assertThat(snackBarUiState.snackBarQueue.first().testTag) - .isEqualTo(SNACKBAR_POST_CAPTURE_VIDEO_DELETE_FAILURE) + assertThat(snackBarUiState.snackBarQueue.first().stringResource) + .isEqualTo(R.string.snackbar_delete_video_failure) } @Test diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt index dfb2beac1..06caa8a50 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt @@ -83,6 +83,7 @@ import com.google.jetpackcamera.ui.components.capture.PauseResumeToggleButton import com.google.jetpackcamera.ui.components.capture.PreviewDisplay import com.google.jetpackcamera.ui.components.capture.PreviewLayout import com.google.jetpackcamera.ui.components.capture.R +import com.google.jetpackcamera.ui.components.capture.SNACKBAR_NODE_TAG import com.google.jetpackcamera.ui.components.capture.ScreenFlashScreen import com.google.jetpackcamera.ui.components.capture.StabilizationIcon import com.google.jetpackcamera.ui.components.capture.TestableSnackbar @@ -576,7 +577,7 @@ private fun ContentScreen( if (snackBarData != null) { snackBarController?.let { snackBarController -> TestableSnackbar( - modifier = modifier.testTag(snackBarData.testTag), + modifier = modifier.testTag(SNACKBAR_NODE_TAG), snackbarToShow = snackBarData, snackbarHostState = snackbarHostState, snackBarController = snackBarController diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt index 7e1909e81..a54fe3988 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt @@ -32,15 +32,16 @@ import com.google.jetpackcamera.feature.preview.navigation.getRequestedSaveMode import com.google.jetpackcamera.model.CaptureEvent import com.google.jetpackcamera.model.DebugSettings import com.google.jetpackcamera.model.ExternalCaptureMode +import com.google.jetpackcamera.model.ImageCaptureEvent import com.google.jetpackcamera.model.IntProgress import com.google.jetpackcamera.model.LowLightBoostState import com.google.jetpackcamera.model.SaveLocation import com.google.jetpackcamera.model.SaveMode +import com.google.jetpackcamera.model.VideoCaptureEvent import com.google.jetpackcamera.settings.SettableConstraintsRepository import com.google.jetpackcamera.settings.SettingsRepository import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.applyExternalCaptureMode -import com.google.jetpackcamera.ui.components.capture.LOW_LIGHT_BOOST_FAILURE_TAG import com.google.jetpackcamera.ui.components.capture.R import com.google.jetpackcamera.ui.controller.CameraController import com.google.jetpackcamera.ui.controller.CaptureController @@ -64,6 +65,7 @@ import com.google.jetpackcamera.ui.uistate.SnackBarUiState import com.google.jetpackcamera.ui.uistate.SnackbarData import com.google.jetpackcamera.ui.uistate.capture.TrackedCaptureUiState import com.google.jetpackcamera.ui.uistate.capture.compound.CaptureUiState +import com.google.jetpackcamera.ui.uistateadapter.capture.R as StateAdapterR import com.google.jetpackcamera.ui.uistateadapter.capture.compound.captureUiState import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -223,7 +225,6 @@ class PreviewViewModel @Inject constructor( }, captureEvents = _captureEvents, imageWellController = imageWellController, - snackBarController = snackBarController, coroutineContext = viewModelScope.coroutineContext ) @@ -260,13 +261,61 @@ class PreviewViewModel @Inject constructor( SnackbarData( cookie = "LowLightBoost-$cookieInt", stringResource = R.string.low_light_boost_error_toast_message, - withDismissAction = true, - testTag = LOW_LIGHT_BOOST_FAILURE_TAG + withDismissAction = true ) ) } } } + + launch { + for (event in captureController.captureEvents) { + showSnackbarForCaptureEvent(event) + _captureEvents.send(event) + } + } + } + } + + private fun showSnackbarForCaptureEvent(event: CaptureEvent) { + val stringRes = when (event) { + is ImageCaptureEvent.ImageCaptureExternalUnsupported -> + StateAdapterR.string.toast_image_capture_external_unsupported + + is VideoCaptureEvent.VideoCaptureExternalUnsupported -> + StateAdapterR.string.toast_video_capture_external_unsupported + + is ImageCaptureEvent.SingleImageSaved, + is ImageCaptureEvent.SequentialImageSaved -> + StateAdapterR.string.toast_image_capture_success + + is ImageCaptureEvent.SingleImageCaptureError, + is ImageCaptureEvent.SequentialImageCaptureError -> + StateAdapterR.string.toast_capture_failure + + is VideoCaptureEvent.VideoSaved -> + StateAdapterR.string.toast_video_capture_success + + is VideoCaptureEvent.VideoCaptureError -> + StateAdapterR.string.toast_video_capture_failure + + else -> null + } + + stringRes?.let { res -> + val cookieInt = snackBarController.incrementAndGetSnackBarCount() + val prefix = when (event) { + is ImageCaptureEvent -> "Image" + is VideoCaptureEvent -> "Video" + else -> "Capture" + } + snackBarController.addSnackBarData( + SnackbarData( + cookie = "$prefix-$cookieInt", + stringResource = res, + withDismissAction = true + ) + ) } } } diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt index a37c00418..f36792669 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt @@ -361,7 +361,6 @@ fun TestableSnackbar( // box seems to need to have some size to be detected by UiAutomator modifier = modifier .size(20.dp) - .testTag(snackbarToShow.testTag) ) { val context = LocalContext.current LaunchedEffect(snackbarToShow) { diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt index 42add38e5..8779adc67 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt @@ -18,15 +18,7 @@ package com.google.jetpackcamera.ui.components.capture const val CAPTURE_BUTTON = "CaptureButton" const val CAPTURE_MODE_TOGGLE_BUTTON = "CaptureModeToggleButton" const val FLIP_CAMERA_BUTTON = "FlipCameraButton" -const val IMAGE_CAPTURE_SUCCESS_TAG = "ImageCaptureSuccessTag" -const val IMAGE_CAPTURE_FAILURE_TAG = "ImageCaptureFailureTag" -const val IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG = "ImageCaptureExternalUnsupportedTag" -const val IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG = - "ImageCaptureUnsupportedConcurrentCameraTag" -const val VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG = "VideoCaptureExternalUnsupportedTag" -const val VIDEO_CAPTURE_SUCCESS_TAG = "VideoCaptureSuccessTag" -const val VIDEO_CAPTURE_FAILURE_TAG = "VideoCaptureFailureTag" -const val LOW_LIGHT_BOOST_FAILURE_TAG = "LowLightBoostFailureTag" +const val SNACKBAR_NODE_TAG = "SnackbarNodeTag" const val IMAGE_WELL_TAG = "ImageWellTag" @@ -36,14 +28,6 @@ const val AMPLITUDE_NONE_TAG = "AmplitudeNoneTag" const val AMPLITUDE_HOT_TAG = "AmplitudeHotTag" const val FOCUS_METERING_INDICATOR_TAG = "FocusMeteringIndicatorTag" -// HDR disabled rationale tags -const val HDR_IMAGE_UNSUPPORTED_ON_DEVICE_TAG = "HdrImageUnsupportedOnDeviceTag" -const val HDR_IMAGE_UNSUPPORTED_ON_LENS_TAG = "HdrImageUnsupportedOnLensTag" -const val HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM_TAG = "HdrImageUnsupportedOnSingleStreamTag" -const val HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG = "HdrImageUnsupportedOnMultiStreamTag" -const val HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG = "HdrVideoUnsupportedOnDeviceTag" -const val HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG = "HdrVideoUnsupportedOnDeviceTag" -const val HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED_TAG = "HdrSimultaneousImageVideoUnsupportedTag" const val ZOOM_BUTTON_ROW_TAG = "ZoomButtonRowTag" const val ZOOM_BUTTON_MIN_TAG = "ZoomButtonMinTag" const val ZOOM_BUTTON_1_TAG = "ZoomButton1Tag" diff --git a/ui/controller/impl/build.gradle.kts b/ui/controller/impl/build.gradle.kts index 7e4a74d36..fb2951ebf 100644 --- a/ui/controller/impl/build.gradle.kts +++ b/ui/controller/impl/build.gradle.kts @@ -70,7 +70,6 @@ dependencies { implementation(libs.kotlinx.atomicfu) implementation(project(":data:media")) - implementation(project(":ui:components:capture")) implementation(project(":ui:controller")) implementation(project(":ui:uistate")) implementation(project(":ui:uistate:capture")) diff --git a/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/CaptureControllerImpl.kt b/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/CaptureControllerImpl.kt index c6fb504e8..90c1a4af3 100644 --- a/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/CaptureControllerImpl.kt +++ b/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/CaptureControllerImpl.kt @@ -29,18 +29,10 @@ import com.google.jetpackcamera.model.IntProgress import com.google.jetpackcamera.model.SaveLocation import com.google.jetpackcamera.model.SaveMode import com.google.jetpackcamera.model.VideoCaptureEvent -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_FAILURE_TAG -import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_FAILURE_TAG -import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG import com.google.jetpackcamera.ui.controller.CaptureController import com.google.jetpackcamera.ui.controller.ImageWellController -import com.google.jetpackcamera.ui.controller.SnackBarController import com.google.jetpackcamera.ui.controller.impl.Utils.nextSaveLocation import com.google.jetpackcamera.ui.controller.impl.Utils.postCurrentMediaToMediaRepository -import com.google.jetpackcamera.ui.uistate.SnackbarData import com.google.jetpackcamera.ui.uistate.capture.TrackedCaptureUiState import kotlin.coroutines.CoroutineContext import kotlinx.atomicfu.atomic @@ -80,7 +72,6 @@ class CaptureControllerImpl( private val externalCapturesCallback: () -> Pair, override val captureEvents: Channel, private val imageWellController: ImageWellController, - private val snackBarController: SnackBarController?, coroutineContext: CoroutineContext ) : CaptureController { @@ -92,14 +83,7 @@ class CaptureControllerImpl( override fun captureImage(contentResolver: ContentResolver) { if (externalCaptureMode == ExternalCaptureMode.VideoCapture) { - snackBarController?.addSnackBarData( - SnackbarData( - cookie = "Image-ExternalVideoCaptureMode", - stringResource = R.string.toast_image_capture_external_unsupported, - withDismissAction = true, - testTag = IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG - ) - ) + captureEvents.trySend(ImageCaptureEvent.ImageCaptureExternalUnsupported) return } Log.d(TAG, "captureImage") @@ -158,19 +142,11 @@ class CaptureControllerImpl( override fun startVideoRecording() { if (externalCaptureMode == ExternalCaptureMode.ImageCapture) { Log.d(TAG, "externalVideoRecording") - snackBarController?.addSnackBarData( - SnackbarData( - cookie = "Video-ExternalImageCaptureMode", - stringResource = R.string.toast_video_capture_external_unsupported, - withDismissAction = true, - testTag = VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG - ) - ) + captureEvents.trySend(VideoCaptureEvent.VideoCaptureExternalUnsupported) return } Log.d(TAG, "startVideoRecording") recordingJob = scope.launch { - val cookie = "Video-${videoCaptureStartedCount.incrementAndGet()}" val (saveLocation, _) = nextSaveLocation( saveMode, externalCaptureMode, @@ -178,7 +154,6 @@ class CaptureControllerImpl( ) try { cameraSystem.startVideoRecording(saveLocation) { - var snackbarToShow: SnackbarData? when (it) { is OnVideoRecordEvent.OnVideoRecorded -> { Log.d(TAG, "cameraSystem.startRecording OnVideoRecorded") @@ -200,34 +175,13 @@ class CaptureControllerImpl( } captureEvents.trySend(event) - // don't display snackbar for successful capture - snackbarToShow = if (saveLocation is SaveLocation.Cache) { - null - } else { - SnackbarData( - cookie = cookie, - stringResource = R.string.toast_video_capture_success, - withDismissAction = true, - testTag = VIDEO_CAPTURE_SUCCESS_TAG - ) - } } is OnVideoRecordEvent.OnVideoRecordError -> { Log.d(TAG, "cameraSystem.startRecording OnVideoRecordError") captureEvents.trySend(VideoCaptureEvent.VideoCaptureError(it.error)) - snackbarToShow = SnackbarData( - cookie = cookie, - stringResource = R.string.toast_video_capture_failure, - withDismissAction = true, - testTag = VIDEO_CAPTURE_FAILURE_TAG - ) } } - - snackbarToShow?.let { data -> - snackBarController?.addSnackBarData(data) - } } Log.d(TAG, "cameraSystem.startRecording success") } catch (exception: IllegalStateException) { @@ -250,41 +204,16 @@ class CaptureControllerImpl( onSuccess: (T) -> Unit = {}, onFailure: (exception: Exception) -> Unit = {} ) { - val cookieInt = snackBarController?.incrementAndGetSnackBarCount() - ?: traceCookie.incrementAndGet() - val cookie = "Image-$cookieInt" - val snackBarData = try { - traceAsync(IMAGE_CAPTURE_TRACE, cookieInt) { + val cookieInt = traceCookie.incrementAndGet() + try { + val result = traceAsync(IMAGE_CAPTURE_TRACE, cookieInt) { doTakePicture() - }.also { result -> - onSuccess(result) } + onSuccess(result) Log.d(TAG, "cameraSystem.takePicture success") - // don't display snackbar for successful capture - if (saveLocation is SaveLocation.Cache) { - null - } else { - SnackbarData( - cookie = cookie, - stringResource = R.string.toast_image_capture_success, - withDismissAction = true, - testTag = IMAGE_CAPTURE_SUCCESS_TAG - ) - } } catch (exception: Exception) { onFailure(exception) Log.d(TAG, "cameraSystem.takePicture error", exception) - SnackbarData( - cookie = cookie, - stringResource = R.string.toast_capture_failure, - withDismissAction = true, - testTag = IMAGE_CAPTURE_FAILURE_TAG - ) - } - snackBarData?.let { - snackBarController?.addSnackBarData( - it - ) } } diff --git a/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/SnackBarControllerImpl.kt b/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/SnackBarControllerImpl.kt index 726535487..31a543b70 100644 --- a/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/SnackBarControllerImpl.kt +++ b/ui/controller/impl/src/main/java/com/google/jetpackcamera/ui/controller/impl/SnackBarControllerImpl.kt @@ -53,8 +53,7 @@ class SnackBarControllerImpl( SnackbarData( cookie = cookie, stringResource = disabledReason.reasonTextResId, - withDismissAction = true, - testTag = disabledReason.testTag + withDismissAction = true ) ) } diff --git a/ui/controller/testing/src/main/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarController.kt b/ui/controller/testing/src/main/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarController.kt index 2c73edb0f..fa52e99fd 100644 --- a/ui/controller/testing/src/main/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarController.kt +++ b/ui/controller/testing/src/main/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarController.kt @@ -41,8 +41,7 @@ class FakeSnackBarController( SnackbarData( cookie = cookie, stringResource = disabledReason.reasonTextResId, - withDismissAction = true, - testTag = disabledReason.testTag + withDismissAction = true ) ) } diff --git a/ui/controller/testing/src/test/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarControllerTest.kt b/ui/controller/testing/src/test/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarControllerTest.kt index 2dc1acbc2..5a37f37a4 100644 --- a/ui/controller/testing/src/test/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarControllerTest.kt +++ b/ui/controller/testing/src/test/java/com/google/jetpackcamera/ui/controller/testing/FakeSnackBarControllerTest.kt @@ -25,7 +25,6 @@ import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class FakeSnackBarControllerTest { private val testDisableRationale = object : DisableRationale { - override val testTag: String = "test-tag" override val reasonTextResId: Int = 123 } @@ -40,7 +39,6 @@ class FakeSnackBarControllerTest { assertThat(calledValue).isNotNull() assertThat(calledValue?.cookie).isEqualTo("DisabledHdrToggle-1") assertThat(calledValue?.stringResource).isEqualTo(testDisableRationale.reasonTextResId) - assertThat(calledValue?.testTag).isEqualTo(testDisableRationale.testTag) } @Test diff --git a/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SingleSelectableUiState.kt b/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SingleSelectableUiState.kt index 508628606..8b523c53f 100644 --- a/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SingleSelectableUiState.kt +++ b/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SingleSelectableUiState.kt @@ -50,7 +50,6 @@ sealed interface SingleSelectableUiState { * Provides a way to get a human-readable message and a test tag for UI testing. */ interface DisableRationale { - val testTag: String val reasonTextResId: Int fun getDisplayMessage(context: Context): String { return context.getString(reasonTextResId) diff --git a/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SnackBarUiState.kt b/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SnackBarUiState.kt index e85ca45d1..1bdedfbc5 100644 --- a/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SnackBarUiState.kt +++ b/ui/uistate/src/main/java/com/google/jetpackcamera/ui/uistate/SnackBarUiState.kt @@ -49,6 +49,5 @@ data class SnackbarData( val stringResource: Int, val duration: SnackbarDuration = SnackbarDuration.Short, val actionLabelRes: Int? = null, - val withDismissAction: Boolean = false, - val testTag: String = "" + val withDismissAction: Boolean = false ) diff --git a/ui/uistateadapter/capture/build.gradle.kts b/ui/uistateadapter/capture/build.gradle.kts index c26f63359..f9c6f2346 100644 --- a/ui/uistateadapter/capture/build.gradle.kts +++ b/ui/uistateadapter/capture/build.gradle.kts @@ -74,7 +74,6 @@ dependencies { implementation(project(":ui:uistate")) implementation(project(":ui:uistateadapter")) implementation(project(":ui:uistate:capture")) - implementation(project(":ui:components:capture")) testImplementation(libs.junit) testImplementation(libs.truth) diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/CaptureModeUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/CaptureModeUiStateAdapter.kt index 11642f18e..b757c7128 100644 --- a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/CaptureModeUiStateAdapter.kt +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/CaptureModeUiStateAdapter.kt @@ -28,7 +28,6 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.CameraConstraints import com.google.jetpackcamera.settings.model.CameraSystemConstraints import com.google.jetpackcamera.settings.model.forCurrentLens -import com.google.jetpackcamera.ui.components.capture.DisabledReason import com.google.jetpackcamera.ui.uistate.SingleSelectableUiState import com.google.jetpackcamera.ui.uistate.capture.CaptureModeToggleUiState import com.google.jetpackcamera.ui.uistate.capture.CaptureModeUiState diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/DisabledReason.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DisabledReason.kt similarity index 70% rename from ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/DisabledReason.kt rename to ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DisabledReason.kt index 50c33e06b..4d2ba8efb 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/DisabledReason.kt +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DisabledReason.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,58 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.jetpackcamera.ui.components.capture +package com.google.jetpackcamera.ui.uistateadapter.capture import com.google.jetpackcamera.ui.uistate.DisableRationale /** - * Represents reasons why a UI component or functionality might be disabled, providing a test tag - * and a string resource ID for user-facing explanation. + * Represents reasons why a UI component or functionality might be disabled, providing + * a string resource ID for user-facing explanation. */ enum class DisabledReason( - // 'override' is required - override val testTag: String, - // 'override' is required override val reasonTextResId: Int ) : DisableRationale { VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED( - VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG, R.string.toast_video_capture_external_unsupported ), IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED( - IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG, R.string.toast_image_capture_external_unsupported ), IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA( - IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG, R.string.toast_image_capture_unsupported_concurrent_camera ), HDR_VIDEO_UNSUPPORTED_ON_DEVICE( - HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG, R.string.toast_hdr_video_unsupported_on_device ), HDR_VIDEO_UNSUPPORTED_ON_LENS( - HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG, R.string.toast_hdr_video_unsupported_on_lens ), HDR_IMAGE_UNSUPPORTED_ON_DEVICE( - HDR_IMAGE_UNSUPPORTED_ON_DEVICE_TAG, R.string.toast_hdr_photo_unsupported_on_device ), HDR_IMAGE_UNSUPPORTED_ON_LENS( - HDR_IMAGE_UNSUPPORTED_ON_LENS_TAG, R.string.toast_hdr_photo_unsupported_on_lens ), HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM( - HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM_TAG, R.string.toast_hdr_photo_unsupported_on_lens_single_stream ), HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM( - HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG, R.string.toast_hdr_photo_unsupported_on_lens_multi_stream ), HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED( - HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED_TAG, R.string.toast_hdr_simultaneous_image_video_unsupported ) } diff --git a/ui/uistateadapter/capture/src/main/res/values/strings.xml b/ui/uistateadapter/capture/src/main/res/values/strings.xml new file mode 100644 index 000000000..cc7b83204 --- /dev/null +++ b/ui/uistateadapter/capture/src/main/res/values/strings.xml @@ -0,0 +1,32 @@ + + + + Image Capture Success + Video Capture Success + Image Capture Failure + Video Capture Failure + Video not supported while app is in image-only capture mode + Image capture not supported while app is in video-only capture mode + Image capture not supported in dual camera mode + Ultra HDR photos not supported on this device + Ultra HDR photos not supported by current lens + Single-stream mode does not support UltraHDR photo capture for current lens + Multi-stream mode does not support UltraHDR photo capture for current lens + HDR video not supported on this device + HDR video not supported by current lens + HDR video and image capture cannot be bound simultaneously +