From 6cc3d37153546f7b2e83561596eae49bbadfcee2 Mon Sep 17 00:00:00 2001 From: donovanfm Date: Wed, 20 Mar 2024 19:00:29 -0400 Subject: [PATCH] Move Toast invocations out of Camera/VideoEdit ViewModels. --- .../samples/socialite/ui/camera/Camera.kt | 17 ++++++++++++++ .../socialite/ui/camera/CameraViewModel.kt | 23 +++++++++++++++---- .../socialite/ui/videoedit/VideoEditScreen.kt | 19 +++++++++++++++ .../ui/videoedit/VideoEditScreenViewModel.kt | 20 ++++++++++++---- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/camera/Camera.kt b/app/src/main/java/com/google/android/samples/socialite/ui/camera/Camera.kt index 3381dcd5..65280b84 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/camera/Camera.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/camera/Camera.kt @@ -19,6 +19,7 @@ package com.google.android.samples.socialite.ui.camera import android.Manifest import android.annotation.SuppressLint import android.view.Surface +import android.widget.Toast import androidx.camera.core.CameraSelector import androidx.camera.core.Preview import androidx.camera.view.RotationProvider @@ -64,6 +65,7 @@ import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState +import kotlin.coroutines.coroutineContext import kotlin.reflect.KFunction1 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor @@ -91,6 +93,21 @@ fun Camera( val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current + LaunchedEffect(lifecycleOwner, context) { + viewModel.imageCaptureState.collect { state -> + when (state) { + ImageCaptureState.IMAGE_CAPTURE_SUCCESS -> + Toast.makeText(context, "Photo saved.", Toast.LENGTH_SHORT).show() + ImageCaptureState.IMAGE_CAPTURE_FAIL -> + Toast.makeText(context, "Photo capture failed.", Toast.LENGTH_SHORT).show() + else -> { + /* no-op */ + } + } + + } + } + var isLayoutUnfolded by remember { mutableStateOf(null) } LaunchedEffect(lifecycleOwner, context) { diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/camera/CameraViewModel.kt b/app/src/main/java/com/google/android/samples/socialite/ui/camera/CameraViewModel.kt index 7036fe3e..6b1ca3d9 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/camera/CameraViewModel.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/camera/CameraViewModel.kt @@ -22,7 +22,6 @@ import android.content.Context import android.os.Build import android.provider.MediaStore import android.view.Display -import android.widget.Toast import androidx.annotation.RequiresPermission import androidx.camera.core.AspectRatio import androidx.camera.core.Camera @@ -58,6 +57,7 @@ import java.text.SimpleDateFormat import java.util.Locale import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch @HiltViewModel @@ -72,6 +72,8 @@ class CameraViewModel @Inject constructor( val chatId: Long? = savedStateHandle.get("chatId") var viewFinderState = MutableStateFlow(ViewFinderState()) + private var _imageCaptureState = MutableStateFlow(ImageCaptureState.PENDING) + val imageCaptureState: SharedFlow = _imageCaptureState val aspectRatioStrategy = AspectRatioStrategy(AspectRatio.RATIO_16_9, AspectRatioStrategy.FALLBACK_RULE_NONE) @@ -189,18 +191,23 @@ class CameraViewModel @Inject constructor( ContextCompat.getMainExecutor(context), object : ImageCapture.OnImageSavedCallback { override fun onError(exc: ImageCaptureException) { - val msg = "Photo capture failed." - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + viewModelScope.launch { + _imageCaptureState.emit(ImageCaptureState.IMAGE_CAPTURE_FAIL) + } } override fun onImageSaved(output: ImageCapture.OutputFileResults) { + var state = ImageCaptureState.PENDING val savedUri = output.savedUri if (savedUri != null) { + state = ImageCaptureState.IMAGE_CAPTURE_SUCCESS sendPhotoMessage(savedUri.toString()) onMediaCaptured(Media(savedUri, MediaType.PHOTO)) } else { - val msg = "Photo capture failed." - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + state = ImageCaptureState.IMAGE_CAPTURE_FAIL + } + viewModelScope.launch { + _imageCaptureState.emit(state) } } }, @@ -310,6 +317,12 @@ data class ViewFinderState( val lensFacing: Int = CameraSelector.LENS_FACING_BACK, ) +enum class ImageCaptureState { + PENDING, + IMAGE_CAPTURE_SUCCESS, + IMAGE_CAPTURE_FAIL +} + /** * Defines the current state of the camera. */ diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreen.kt b/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreen.kt index ef6db537..b1cf5ddc 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreen.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreen.kt @@ -19,6 +19,7 @@ package com.google.android.samples.socialite.ui.videoedit import android.media.MediaMetadataRetriever import android.net.Uri import android.util.Log +import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -59,6 +60,7 @@ import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -71,6 +73,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -92,6 +95,7 @@ fun VideoEditScreen( onCloseButtonClicked: () -> Unit, navController: NavController, ) { + val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current val viewModel: VideoEditScreenViewModel = hiltViewModel() @@ -104,6 +108,21 @@ fun VideoEditScreen( val isProcessing = viewModel.isProcessing.collectAsState() + LaunchedEffect(lifecycleOwner, context) { + viewModel.videoSaveState.collect { state -> + when (state) { + VideoSaveState.VIDEO_SAVE_SUCCESS -> + Toast.makeText(context, "Edited video saved", Toast.LENGTH_LONG).show() + VideoSaveState.VIDEO_SAVE_FAIL -> + Toast.makeText(context, "Error applying edits on video", Toast.LENGTH_LONG) + .show() + else -> { + /* no-op */ + } + } + } + } + var removeAudioEnabled by rememberSaveable { mutableStateOf(false) } var overlayText by rememberSaveable { mutableStateOf("") } var redOverlayTextEnabled by rememberSaveable { mutableStateOf(false) } diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreenViewModel.kt b/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreenViewModel.kt index 8b9d52d8..e22a18a8 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreenViewModel.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/videoedit/VideoEditScreenViewModel.kt @@ -23,7 +23,6 @@ import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan -import android.widget.Toast import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.media3.common.MediaItem @@ -49,6 +48,7 @@ import java.text.SimpleDateFormat import java.util.Locale import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -67,6 +67,9 @@ class VideoEditScreenViewModel @Inject constructor( private val _isProcessing = MutableStateFlow(false) val isProcessing: StateFlow = _isProcessing + private var _videoSaveState = MutableStateFlow(VideoSaveState.PENDING) + val videoSaveState: SharedFlow = _videoSaveState + fun setChatId(chatId: Long) { this.chatId.value = chatId } @@ -74,7 +77,9 @@ class VideoEditScreenViewModel @Inject constructor( private val transformerListener: Transformer.Listener = @UnstableApi object : Transformer.Listener { override fun onCompleted(composition: Composition, exportResult: ExportResult) { - Toast.makeText(application, "Edited video saved", Toast.LENGTH_LONG).show() + viewModelScope.launch { + _videoSaveState.emit(VideoSaveState.VIDEO_SAVE_SUCCESS) + } sendVideo() @@ -88,8 +93,9 @@ class VideoEditScreenViewModel @Inject constructor( exportException: ExportException, ) { exportException.printStackTrace() - Toast.makeText(application, "Error applying edits on video", Toast.LENGTH_LONG) - .show() + viewModelScope.launch { + _videoSaveState.emit(VideoSaveState.VIDEO_SAVE_FAIL) + } _isProcessing.value = false } } @@ -183,3 +189,9 @@ class VideoEditScreenViewModel @Inject constructor( } } } + +enum class VideoSaveState { + PENDING, + VIDEO_SAVE_SUCCESS, + VIDEO_SAVE_FAIL +}