diff --git a/presenter/src/main/java/com/foke/together/presenter/component/Basic.kt b/presenter/src/main/java/com/foke/together/presenter/component/Basic.kt index 6ca99b9..0fd32af 100644 --- a/presenter/src/main/java/com/foke/together/presenter/component/Basic.kt +++ b/presenter/src/main/java/com/foke/together/presenter/component/Basic.kt @@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Remove import androidx.compose.material.icons.filled.Start +import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Button import androidx.compose.material3.ButtonColors import androidx.compose.material3.Card @@ -288,12 +289,16 @@ fun AppTopBar( when(alignment){ Alignment.CenterHorizontally -> { CenterAlignedTopAppBar( + modifier = Modifier.fillMaxWidth().wrapContentSize().padding( + start = AppTheme.size.layoutPadding, + end = AppTheme.size.layoutPadding + ), title = { Text( text = title, style = titleStyle, textAlign = TextAlign.Center, - color = AppTheme.colorScheme.border + color = AppTheme.colorScheme.top ) }, colors = TopAppBarDefaults.topAppBarColors( @@ -309,6 +314,10 @@ fun AppTopBar( } Alignment.Start -> { TopAppBar( + modifier = Modifier.fillMaxWidth().wrapContentSize().padding( + start = AppTheme.size.layoutPadding, + end = AppTheme.size.layoutPadding + ), title = { Text( text = title, @@ -431,4 +440,26 @@ fun AppTextField( ), enabled = enabled ) +} + +@Composable +fun AppBottomBar( + content: @Composable (RowScope.() -> Unit) +){ + BottomAppBar( + modifier = Modifier.fillMaxWidth().padding( + start = AppTheme.size.layoutPadding, + end = AppTheme.size.layoutPadding, + bottom = AppTheme.size.layoutPadding + ), + containerColor = AppTheme.colorScheme.bottom, + contentColor = AppTheme.colorScheme.top, + ){ + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + content = content + ) + } } \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/navigation/NavGraph.kt b/presenter/src/main/java/com/foke/together/presenter/navigation/NavGraph.kt index 3bb2b2a..2152536 100644 --- a/presenter/src/main/java/com/foke/together/presenter/navigation/NavGraph.kt +++ b/presenter/src/main/java/com/foke/together/presenter/navigation/NavGraph.kt @@ -70,7 +70,7 @@ private fun addSelectFrameScreen( navGraphBuilder.composable(route = NavRoute.SelectFrame.path) { SelectFrameScreen( navigateToMethod = { - navController.navigate(NavRoute.SelectMethod.path) + navController.navigate(NavRoute.InternalCamera.path) }, popBackStack = { navController.popBackStack(NavRoute.Home.path, inclusive = false) diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/HomeScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/HomeScreen.kt index 2494786..50a67b7 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/HomeScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/HomeScreen.kt @@ -22,8 +22,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview @@ -97,12 +100,13 @@ fun HomeScreenContent( verticalArrangement = Arrangement.SpaceEvenly, horizontalAlignment = Alignment.CenterHorizontally ){ - Image( - painter = painterResource( - id = R.drawable.fourcut_together + Icon( + imageVector = ImageVector.vectorResource( + id = R.drawable.fourcut_together, ), contentDescription = "FourCuts Together", modifier = Modifier.wrapContentSize(), + tint = AppTheme.colorScheme.top ) AppButton( onClick = { navigationSelectFrame() }, diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/InternalCameraScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/InternalCameraScreen.kt index 0d16822..f544e81 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/InternalCameraScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/InternalCameraScreen.kt @@ -2,6 +2,12 @@ package com.foke.together.presenter.screen import android.content.Context import androidx.camera.view.PreviewView +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text @@ -20,12 +27,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.hilt.navigation.compose.hiltViewModel @@ -34,9 +45,11 @@ import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.foke.together.presenter.R +import com.foke.together.presenter.component.AppBottomBar import com.foke.together.presenter.component.AppTopBar import com.foke.together.presenter.component.BasicScaffold import com.foke.together.presenter.screen.state.InternalCameraState +import com.foke.together.presenter.theme.AppTheme import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.viewmodel.InternelCameraViewModel import com.foke.together.util.AppLog @@ -55,6 +68,8 @@ fun InternalCameraScreenRoot( val context = LocalContext.current val captureCount by viewModel.captureCount.collectAsStateWithLifecycle() val progress by viewModel.progressState.collectAsStateWithLifecycle() + val isFlashAnimationVisible by viewModel.flashAnimationState.collectAsStateWithLifecycle() + val countdownSeconds by viewModel.countdownSeconds.collectAsStateWithLifecycle() LifecycleEventEffect(Lifecycle.Event.ON_START) { viewModel.setCaptureTimer(context) { navigateToGenerateImage() } AppLog.d(TAG, "LifecycleEventEffect. ON_START", "") @@ -71,6 +86,8 @@ fun InternalCameraScreenRoot( state = state, captureCount = captureCount, progress = progress, + isFlashAnimationVisible = isFlashAnimationVisible, + countdownSeconds = countdownSeconds, initialPreview = { context, previewView -> viewModel.initial(context, lifecycleOwner, previewView) }, @@ -86,9 +103,16 @@ fun InternalCameraScreen( state : InternalCameraState, captureCount : Int, progress: Float, + isFlashAnimationVisible: Boolean, + countdownSeconds: Int, initialPreview : (Context, PreviewView) -> Unit, releasePreview : (Context) -> Unit, ){ + val previewAlpha by animateFloatAsState( + targetValue = if (isFlashAnimationVisible) 0.0f else 1.0f, + animationSpec = tween(durationMillis = if (isFlashAnimationVisible) 50 else 150), + label = "preview_alpha" + ) BasicScaffold( modifier = Modifier.fillMaxSize(), topBar = { @@ -98,7 +122,14 @@ fun InternalCameraScreen( ) }, bottomBar = { - BottomAppBar { } + AppBottomBar{ + Text( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + text = stringResource(id = R.string.camera_exclamation_text), + style = AppTheme.typography.title, + textAlign = TextAlign.Center + ) + } } ){ paddingValues -> Column( @@ -111,28 +142,50 @@ fun InternalCameraScreen( verticalArrangement = Arrangement.SpaceEvenly, horizontalAlignment = Alignment.CenterHorizontally, ){ - Text( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - text = stringResource(id = R.string.camera_exclamation_text), - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - textAlign = TextAlign.Center - ) Box( - modifier = Modifier.aspectRatio(1.5f).fillMaxHeight() + modifier = Modifier.aspectRatio(1.5f).fillMaxHeight(0.8f), ) { AndroidView( - modifier = Modifier.align(Alignment.Center).fillMaxSize(), + modifier = Modifier + .align(Alignment.Center) + .fillMaxSize() + .clip(RoundedCornerShape(AppTheme.size.icon)) + .alpha(previewAlpha) + .background( + color = AppTheme.colorScheme.bottom + ), factory = { context -> PreviewView(context).also { preview -> initialPreview(context, preview) + } }, onRelease = { previewView -> releasePreview(previewView.context) } - ) + + // Countdown overlay + this@Column.AnimatedVisibility( + visible = countdownSeconds > 0, + enter = scaleIn(), + exit = scaleOut(), + modifier = Modifier.align(Alignment.Center) + ) { + Text( + text = countdownSeconds.toString(), + fontSize = 80.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + modifier = Modifier + .background( + Color.Black.copy(alpha = 0.5f), + shape = AppTheme.shapes.container + ) + .padding(24.dp) + ) + } + } } } @@ -147,11 +200,15 @@ fun InternalScreenPreview(){ val state by remember { mutableStateOf(InternalCameraState()) } val captureCount by remember{ mutableStateOf(1) } val progress by remember{ mutableStateOf(1f)} + val isFlashAnimationVisible by remember{ mutableStateOf(false)} + val countdownSeconds by remember{ mutableStateOf(3)} FourCutTogetherTheme() { InternalCameraScreen( state = state, captureCount = captureCount, progress = progress, + isFlashAnimationVisible = isFlashAnimationVisible, + countdownSeconds = countdownSeconds, initialPreview = { context, previewView -> }, diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/SelectFrameScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/SelectFrameScreen.kt index 78614ef..b3b0dcd 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/SelectFrameScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/SelectFrameScreen.kt @@ -1,39 +1,22 @@ package com.foke.together.presenter.screen import android.content.res.Configuration -import android.net.Uri import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.PageSize -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.Button import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarColors import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState @@ -43,24 +26,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.UiMode -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension import androidx.hilt.navigation.compose.hiltViewModel import com.foke.together.domain.interactor.entity.CutFrame import com.foke.together.domain.interactor.entity.DefaultCutFrameSet -import com.foke.together.presenter.R +import com.foke.together.presenter.component.AppBottomBar import com.foke.together.presenter.component.AppTopBar import com.foke.together.presenter.component.BasicScaffold -import com.foke.together.presenter.frame.DefaultCutFrame import com.foke.together.presenter.theme.AppTheme import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.viewmodel.SelectFrameViewModel @@ -73,6 +47,7 @@ fun SelectFrameScreen( ) { val cutFrames by viewModel.cutFrames.collectAsState() val isDateDisplay by viewModel.isDateDisplay.collectAsState() + val isQRDisplay by viewModel.isQRDisplay.collectAsState() DisposableEffect(Unit) { viewModel.updateSessionStatus() @@ -82,12 +57,16 @@ fun SelectFrameScreen( SelectFrameContent( cutFrames = cutFrames, isDateDisplay = isDateDisplay, + isQRDisplay = isQRDisplay, selectFrame = { viewModel.setCutFrameType(it) navigateToMethod() }, - onChangeData = { - viewModel.updateDateDisplay() + onDisplayDate = { isDateDisplay -> + viewModel.updateDateDisplay(isDateDisplay) + }, + onDisplayQR ={ isQRDisplay -> + viewModel.updateQRDisplay(isQRDisplay) } ) } @@ -97,8 +76,10 @@ fun SelectFrameScreen( fun SelectFrameContent( cutFrames: List, isDateDisplay: Boolean, + isQRDisplay : Boolean, selectFrame: (cutFrame: CutFrame) -> Unit, - onChangeData : ( Boolean ) -> Unit + onDisplayDate : (Boolean ) -> Unit, + onDisplayQR : (Boolean) -> Unit, ){ BasicScaffold( modifier = Modifier.fillMaxSize(), @@ -109,20 +90,15 @@ fun SelectFrameContent( ) }, bottomBar = { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceEvenly - ){ + AppBottomBar { Column( modifier = Modifier.wrapContentSize(), - verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ){ Checkbox( checked = isDateDisplay, onCheckedChange = { isChecked -> - onChangeData(isChecked) + onDisplayDate(isChecked) } ) Text( @@ -133,13 +109,12 @@ fun SelectFrameContent( Column( modifier = Modifier.wrapContentSize(), - verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ){ Checkbox( - checked = isDateDisplay, + checked = isQRDisplay, onCheckedChange = { isChecked -> - onChangeData(isChecked) + onDisplayQR(isChecked) } ) Text( @@ -191,11 +166,14 @@ private fun DefaultPreview() { FourCutTogetherTheme { val cutFrames by remember { mutableStateOf(DefaultCutFrameSet.entries) } var isDateDisplay by remember { mutableStateOf(false) } + var isQRDisplay by remember { mutableStateOf(false) } SelectFrameContent( cutFrames = cutFrames, isDateDisplay = isDateDisplay, + isQRDisplay = isQRDisplay, selectFrame = {}, - onChangeData = { isDateDisplay = it } + onDisplayDate = { isDateDisplay = it }, + onDisplayQR = { isQRDisplay = it } ) } } \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/ShareScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/ShareScreen.kt index 49b1c3b..2f068c7 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/ShareScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/ShareScreen.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.net.Uri import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -13,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons @@ -52,6 +54,7 @@ import com.foke.together.presenter.viewmodel.ShareViewModel import com.foke.together.util.AppLog import com.foke.together.util.ImageFileUtil import androidx.core.graphics.createBitmap +import com.foke.together.presenter.component.AppBottomBar @Composable fun ShareScreen( @@ -69,7 +72,8 @@ fun ShareScreen( ShareContent( context = context, captureImageUri = captureImageUri, - qrImageBitmap = qrImageBitmap + qrImageBitmap = qrImageBitmap, + popBackStack = popBackStack, ) } @@ -77,35 +81,37 @@ fun ShareScreen( fun ShareContent( context: Context, captureImageUri : Uri, - qrImageBitmap : Bitmap? + qrImageBitmap : Bitmap?, + popBackStack: () -> Unit, ){ BasicScaffold( modifier = Modifier.fillMaxSize(), topBar = { AppTopBar( title = "이미지 저장", - alignment = Alignment.CenterHorizontally + alignment = Alignment.CenterHorizontally, + leftIcon = { + Icon( + modifier = Modifier.size(AppTheme.size.icon) + .clickable(true, onClick = popBackStack), + imageVector = Icons.Filled.Home, + contentDescription = "home", + tint = AppTheme.colorScheme.top + ) + } ) }, bottomBar = { - Row( - modifier = Modifier.fillMaxWidth().padding( - start = AppTheme.size.layoutPadding, - end = AppTheme.size.layoutPadding, - bottom = AppTheme.size.layoutPadding - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceEvenly - ){ + AppBottomBar{ Column( modifier = Modifier.wrapContentSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ){ Icon( - imageVector = Icons.Outlined.Print, + imageVector = Icons.Filled.Print, contentDescription = "print", - tint = AppTheme.colorScheme.border + tint = AppTheme.colorScheme.top ) Text( text = "출력", @@ -120,9 +126,9 @@ fun ShareContent( horizontalAlignment = Alignment.CenterHorizontally ){ Icon( - imageVector = Icons.Outlined.Download, + imageVector = Icons.Filled.Download, contentDescription = "download", - tint = AppTheme.colorScheme.border + tint = AppTheme.colorScheme.top ) Text( text = "저장", @@ -137,9 +143,9 @@ fun ShareContent( horizontalAlignment = Alignment.CenterHorizontally ){ Icon( - imageVector = Icons.Outlined.Share, + imageVector = Icons.Filled.Share, contentDescription = "sharing", - tint = AppTheme.colorScheme.border + tint = AppTheme.colorScheme.top ) Text( text = "공유", @@ -205,7 +211,8 @@ private fun ShareScreenPreview() { ShareContent( context = LocalContext.current, captureImageUri = Uri.EMPTY, - qrImageBitmap = qrBitmap + qrImageBitmap = qrBitmap, + popBackStack = {} ) } } \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/InternelCameraViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/InternelCameraViewModel.kt index be504f1..78cdefa 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/InternelCameraViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/InternelCameraViewModel.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.foke.together.domain.GetCaptureDurationUseCase import com.foke.together.domain.interactor.GeneratePhotoFrameUseCaseV1 import com.foke.together.domain.interactor.GetInternalCameraCaptureModeUseCase import com.foke.together.domain.interactor.GetInternalCameraFlashModeUseCase @@ -30,11 +31,16 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.delay import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit +import kotlin.time.toDuration @HiltViewModel class InternelCameraViewModel @Inject constructor( private val internalCameraUseCase: InternalCameraUseCase, + private val getCaptureDurationUseCase: GetCaptureDurationUseCase, private val getInternalCameraLensFacingUseCase: GetInternalCameraLensFacingUseCase, private val getInternalCameraFlashModeUseCase: GetInternalCameraFlashModeUseCase, private val getInternalCameraCaptureModeUseCase: GetInternalCameraCaptureModeUseCase, @@ -45,6 +51,13 @@ class InternelCameraViewModel @Inject constructor( // TODO(MutableStateFlow 로 처리하기) val captureCount = MutableStateFlow(1) val progressState = MutableStateFlow(1f) + val flashAnimationState = MutableStateFlow(false) + val countdownSeconds = MutableStateFlow(0) + private val captureDuration = getCaptureDurationUseCase().stateIn( + scope = viewModelScope, + started = SharingStarted.Lazily, + initialValue = AppPolicy.CAPTURE_INTERVAL.toDuration(DurationUnit.MILLISECONDS) + ) private var captureTimer: CountDownTimer? = null private var mTimerState = false private val cameraSelector = getInternalCameraLensFacingUseCase().map { cameraSelectorIdx -> @@ -53,19 +66,19 @@ class InternelCameraViewModel @Inject constructor( .build() }.stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Lazily, initialValue = CameraSelector.DEFAULT_FRONT_CAMERA ) private val captureMode = getInternalCameraCaptureModeUseCase().stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(subscribeDuration), + started = SharingStarted.Lazily, initialValue = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY ) private val flashMode = getInternalCameraFlashModeUseCase().stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(subscribeDuration), + started = SharingStarted.Lazily, initialValue = ImageCapture.FLASH_MODE_OFF ) @@ -102,8 +115,15 @@ class InternelCameraViewModel @Inject constructor( fun capture( context: Context, ) = viewModelScope.launch { - val fileName = "${AppPolicy.CAPTURED_FOUR_CUT_IMAGE_NAME}_${System.currentTimeMillis()}_${captureCount}" + // 플래시 애니메이션 트리거 + flashAnimationState.value = true + + val fileName = "${AppPolicy.CAPTURED_FOUR_CUT_IMAGE_NAME}_${System.currentTimeMillis()}_${captureCount.value}" internalCameraUseCase.capture(context, fileName) + + // 200ms 후 플래시 애니메이션 종료 + delay(200) + flashAnimationState.value = false } fun setCaptureTimer( @@ -119,24 +139,33 @@ class InternelCameraViewModel @Inject constructor( generatePhotoFrameUseCaseV1.clearCapturedImageList() } - captureTimer = object : CountDownTimer(CAPTURE_INTERVAL, COUNTDOWN_INTERVAL) { + captureTimer = object : CountDownTimer(captureDuration.value.toLong(DurationUnit.MILLISECONDS), COUNTDOWN_INTERVAL) { override fun onTick(millisUntilFinished: Long) { progressState.value = 1f - (millisUntilFinished.toFloat() / CAPTURE_INTERVAL) + countdownSeconds.value = ((millisUntilFinished / 1000) + 1).toInt() } override fun onFinish() { + countdownSeconds.value = 0 SoundUtil.getCameraSound(context = context ) // TODO: 현재 External 실패 시, 스크린 캡쳐 화면을 사용하도록 구성함 - capture(context) - progressState.value = 1f - if (captureCount.value < AppPolicy.CAPTURE_COUNT) { - AppLog.d(TAG, "captureCount", "$captureCount") - captureCount.value += 1 - start() - } else { - AppLog.d(TAG, "captureCount", "$captureCount") - stopCaptureTimer() - captureCount.value = 1 - nextNavigate() + + viewModelScope.launch { + capture(context) + progressState.value = 1f + + if (captureCount.value < AppPolicy.CAPTURE_COUNT) { + AppLog.d(TAG, "captureCount", "${captureCount.value}") + captureCount.value += 1 + // 촬영 후 2초 대기 + delay(2.seconds.toLong(DurationUnit.MILLISECONDS)) + start() + } else { + AppLog.d(TAG, "captureCount", "${captureCount.value}") + stopCaptureTimer() + delay(2.seconds.toLong(DurationUnit.MILLISECONDS)) + captureCount.value = 1 + nextNavigate() + } } } } diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/SelectFrameViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/SelectFrameViewModel.kt index 78a86e1..9703595 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/SelectFrameViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/SelectFrameViewModel.kt @@ -19,18 +19,29 @@ class SelectFrameViewModel @Inject constructor( val cutFrames = MutableStateFlow(DefaultCutFrameSet.entries) val isDateDisplay = MutableStateFlow(false) + val isQRDisplay = MutableStateFlow(false) fun updateSessionStatus() { updateSessionStatusUseCase(Status.SELECT_FRAME) } - fun updateDateDisplay() = viewModelScope.launch{ - isDateDisplay.value = !isDateDisplay.value + fun updateDateDisplay(state: Boolean) = viewModelScope.launch{ + isDateDisplay.value = state cutFrames.value = cutFrames.value.map { it.isDateString = isDateDisplay.value it } } + // TODO(QR 표시 기능 구현) + fun updateQRDisplay( state: Boolean ) = viewModelScope.launch{ + isQRDisplay.value = state + cutFrames.value = cutFrames.value.map { + it +// it.isQRString = isQRDisplay.value +// it + } + } + fun setCutFrameType(cutFrame: CutFrame) = viewModelScope.launch { updateSessionStatusUseCase(cutFrame) }