diff --git a/domain/src/main/java/com/foke/together/domain/interactor/ClearCapturedImageUriUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/ClearCapturedImageUriUseCase.kt new file mode 100644 index 0000000..512df06 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/ClearCapturedImageUriUseCase.kt @@ -0,0 +1,12 @@ +package com.foke.together.domain.interactor + +import android.net.Uri +import com.foke.together.domain.output.InternalCameraRepositoryInterface +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class ClearCapturedImageUriUseCase @Inject constructor( + private val internalCameraRepository: InternalCameraRepositoryInterface +) { + operator fun invoke() = internalCameraRepository.clearCapturedImageUri() +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt b/domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt index 3622bcf..c3ffb2b 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt @@ -163,6 +163,44 @@ data object FourCurDark: DefaultCutFrameSet( qrCodePosition = QrStickerPosition(30,145,515) ) + data object FourCutFrame25: DefaultCutFrameSet( + 9, + "FourCutFrame Dark 2025", + 4, 190, 570, + R.drawable.fourcut_frame_dark_25, + listOf( + PhotoPosition(163, 110, 13, 29), + PhotoPosition(163, 110, 13, 144), + PhotoPosition(163, 110, 13, 259), + PhotoPosition(163, 110, 13, 373), + ), + listOf( + R.drawable.fourcut_frame_dark_25 + ), + datePosition = TextStickerPosition(80, 10, 92,546,R.font.cascadia_mono, color = Color(187, 187, 187), fontWeight = FontWeight.ExtraBold, fontSize = 6, textAlign = TextAlign.End), + copyrightPosition = TextStickerPosition(80, 10, 92,551,R.font.cascadia_mono, color = Color(187, 187, 187), fontWeight = FontWeight.ExtraBold, fontSize = 6, textAlign = TextAlign.End), + qrCodePosition = QrStickerPosition(30,145,515) + ) + + data object Halloween25: DefaultCutFrameSet( + 10, + "Halloween 2025", + 4, 190, 570, + R.drawable.holloween, + listOf( + PhotoPosition(163, 110, 13, 29), + PhotoPosition(163, 110, 13, 144), + PhotoPosition(163, 110, 13, 259), + PhotoPosition(163, 110, 13, 373), + ), + listOf( + R.drawable.holloween + ), + datePosition = TextStickerPosition(80, 10, 92,546,R.font.cascadia_mono, color = Color(187, 187, 187), fontWeight = FontWeight.ExtraBold, fontSize = 6, textAlign = TextAlign.End), + copyrightPosition = TextStickerPosition(80, 10, 92,551,R.font.cascadia_mono, color = Color(187, 187, 187), fontWeight = FontWeight.ExtraBold, fontSize = 6, textAlign = TextAlign.End), + qrCodePosition = QrStickerPosition(30,145,515) + ) + companion object { val entries = listOf( FourCutLight, @@ -172,6 +210,8 @@ data object FourCurDark: DefaultCutFrameSet( MakerFaire25_2, MakerFaire25_3, MakerFaire25_4, + FourCutFrame25, + Halloween25, ).sortedBy { it.index } } } \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt index 2abace4..60726d7 100644 --- a/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt +++ b/domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt @@ -17,6 +17,8 @@ interface InternalCameraRepositoryInterface { context: Context, fileName : String, ) + + fun clearCapturedImageUri() suspend fun initial( context: Context, lifecycleOwner: LifecycleOwner, diff --git a/domain/src/main/res/drawable/holloween.png b/domain/src/main/res/drawable/holloween.png new file mode 100644 index 0000000..0e1e595 Binary files /dev/null and b/domain/src/main/res/drawable/holloween.png differ diff --git a/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt b/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt index 1442b05..1916219 100644 --- a/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt +++ b/external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt @@ -70,6 +70,10 @@ class InternalCameraRepository @Inject constructor( } } + override fun clearCapturedImageUri() { + capturedImageUri.value = null + } + override suspend fun initial( context: Context, lifecycleOwner: LifecycleOwner, 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 8a82854..cc2248e 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 @@ -11,7 +11,9 @@ 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.height import androidx.compose.foundation.layout.padding +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 @@ -31,8 +33,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource 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.hilt.navigation.compose.hiltViewModel import com.foke.together.domain.interactor.entity.CutFrame import com.foke.together.domain.interactor.entity.DefaultCutFrameSet @@ -148,27 +152,45 @@ fun SelectFrameContent( ){ items(cutFrames.size){ index -> Box( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .height(450.dp) .clickable{ selectFrame(cutFrames[index]) }, - contentAlignment = Alignment.Center, ){ - DefaultCutFrame( - cutFrame = cutFrames[index], - imageUrlList = listOf( - "file:///android_asset/sample_cut.png".toUri(), - "file:///android_asset/sample_cut.png".toUri(), - "file:///android_asset/sample_cut.png".toUri(), - "file:///android_asset/sample_cut.png".toUri(), - ), - ) - Text( - modifier = Modifier.align(Alignment.TopCenter), - text = cutFrames[index].frameTitle, - style = AppTheme.typography.body.copy(fontWeight = FontWeight.Bold), - color = AppTheme.colorScheme.top - ) + Box( + modifier = Modifier + .align(Alignment.TopCenter) + .height(400.dp) + .width(130.dp) + .clickable{ + selectFrame(cutFrames[index]) + }, + ) { + DefaultCutFrame( + cutFrame = cutFrames[index], + imageUrlList = listOf( + // "file:///android_asset/sample_cut.png".toUri(), + // "file:///android_asset/sample_cut.png".toUri(), + // "file:///android_asset/sample_cut.png".toUri(), + // "file:///android_asset/sample_cut.png".toUri(), + Uri.EMPTY, + Uri.EMPTY, + Uri.EMPTY, + Uri.EMPTY, + ), + ) + Text( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(top = 410.dp) + .width(150.dp), + text = cutFrames[index].frameTitle, + style = AppTheme.typography.body.copy(fontWeight = FontWeight.Bold), + color = AppTheme.colorScheme.top, + textAlign = TextAlign.Center + ) + } } } } 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 7583a06..bcbffbc 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 @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Print @@ -49,7 +50,13 @@ fun ShareScreen( val qrImageBitmap = viewModel.qrCodeBitmap DisposableEffect(Unit) { viewModel.updateSessionStatus() - onDispose { } + viewModel.setupTimer( + finished = popBackStack + ) + viewModel.startTimer() + onDispose { + viewModel.closeTimer() + } } ShareContent( @@ -91,15 +98,6 @@ fun ShareContent( AppTopBar( title = "이미지 저장", 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 = { @@ -163,6 +161,25 @@ fun ShareContent( color = AppTheme.colorScheme.border ) } + + Column( + modifier = Modifier.wrapContentSize() + .clickable(true, onClick = popBackStack), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ + Icon( + modifier = Modifier.size(AppTheme.size.icon), + imageVector = Icons.Filled.Close, + contentDescription = "close", + tint = AppTheme.colorScheme.top + ) + Text( + text = "닫기", + style = AppTheme.typography.title, + color = AppTheme.colorScheme.border + ) + } } }, ){ paddingValues -> 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 6f86c86..956251f 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 @@ -8,6 +8,7 @@ import androidx.camera.view.PreviewView import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.foke.together.domain.interactor.ClearCapturedImageUriUseCase import com.foke.together.domain.interactor.GeneratePhotoFrameUseCaseV1 import com.foke.together.domain.interactor.GetCaptureDurationUseCase import com.foke.together.domain.interactor.GetCapturedImageUriUseCase @@ -35,12 +36,13 @@ import kotlin.time.DurationUnit class InternelCameraViewModel @Inject constructor( private val internalCameraUseCase: InternalCameraUseCase, private val generatePhotoFrameUseCaseV1: GeneratePhotoFrameUseCaseV1, - getCaptureDurationUseCase: GetCaptureDurationUseCase, - getInternalCameraLensFacingUseCase: GetInternalCameraLensFacingUseCase, - getInternalCameraFlashModeUseCase: GetInternalCameraFlashModeUseCase, - getInternalCameraCaptureModeUseCase: GetInternalCameraCaptureModeUseCase, - getCapturedImageUriUseCase: GetCapturedImageUriUseCase, - getSessionUseCase : GetCurrentSessionUseCase, + private val getCaptureDurationUseCase: GetCaptureDurationUseCase, + private val getInternalCameraLensFacingUseCase: GetInternalCameraLensFacingUseCase, + private val getInternalCameraFlashModeUseCase: GetInternalCameraFlashModeUseCase, + private val getInternalCameraCaptureModeUseCase: GetInternalCameraCaptureModeUseCase, + private val getCapturedImageUriUseCase: GetCapturedImageUriUseCase, + private val clearCapturedImageUriUseCase: ClearCapturedImageUriUseCase, + private val getSessionUseCase : GetCurrentSessionUseCase, ): ViewModel() { // TODO(MutableStateFlow 로 처리하기) @@ -141,6 +143,7 @@ class InternelCameraViewModel @Inject constructor( captureTimer = object : CountDownTimer(captureDuration.value.toLong(DurationUnit.MILLISECONDS), AppPolicy.COUNTDOWN_INTERVAL) { override fun onTick(millisUntilFinished: Long) { + clearCapturedImageUriUseCase() countdownSeconds.value = ((millisUntilFinished / 1000) + 1).toInt() } override fun onFinish() { diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/ShareViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/ShareViewModel.kt index d1d0dc0..e2b643f 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/ShareViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/ShareViewModel.kt @@ -2,6 +2,7 @@ package com.foke.together.presenter.viewmodel import android.content.Context import android.net.Uri +import android.os.CountDownTimer import androidx.core.content.FileProvider import androidx.core.net.toFile import androidx.lifecycle.ViewModel @@ -11,11 +12,14 @@ import com.foke.together.domain.interactor.entity.Status import com.foke.together.domain.interactor.session.ClearSessionUseCase import com.foke.together.domain.interactor.session.GetCurrentSessionUseCase import com.foke.together.domain.interactor.session.UpdateSessionStatusUseCase +import com.foke.together.util.AppPolicy import com.foke.together.util.ImageFileUtil import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.time.Duration.Companion.minutes +import kotlin.time.DurationUnit @HiltViewModel class ShareViewModel @Inject constructor( @@ -29,6 +33,8 @@ class ShareViewModel @Inject constructor( val singleImageUri: Uri = generatePhotoFrameUseCaseV1.getFinalSingleImageUri() val twoImageUri: Uri = generatePhotoFrameUseCaseV1.getFinalTwoImageUri() + private var returnHomeTimer: CountDownTimer? = null + fun downloadImage(): Result { return getCurrentSessionUseCase()?.let { session -> val imageBitmap = ImageFileUtil.getBitmapFromUri(context, singleImageUri) @@ -62,6 +68,29 @@ class ShareViewModel @Inject constructor( updateSessionStatusUseCase(Status.SHARE) } + fun setupTimer( + finished : () -> Unit + ){ + returnHomeTimer = object : CountDownTimer(1.minutes.toLong(DurationUnit.MILLISECONDS), AppPolicy.COUNTDOWN_INTERVAL){ + override fun onFinish() { + finished() + } + override fun onTick(millisUntilFinished: Long) { + // + } + } + } + fun startTimer() { + returnHomeTimer?.start() + } + + fun closeTimer( + + ){ + returnHomeTimer?.cancel() + returnHomeTimer = null + } + fun closeSession() { clearSessionUseCase() }