From 89b4a24dde069666e4e60e4698da591e11732f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9C=A4=EC=88=98?= Date: Wed, 9 Oct 2024 17:32:26 +0900 Subject: [PATCH 1/3] Implement image capture and processing features with external camera support --- .../CaptureWithExternalCameraUseCase.kt | 14 +++-- .../interactor/GeneratePhotoFrameUseCase.kt | 22 ++++++++ .../domain/interactor/entity/CutFrameType.kt | 26 ++++++++++ .../domain/output/ImageRepositoryInterface.kt | 20 +++++++ .../external/repository/ImageRepository.kt | 52 +++++++++++++++++++ .../repository/di/RepositoryModule.kt | 7 +++ .../presenter/viewmodel/CameraViewModel.kt | 2 +- .../com/foke/together/util/ImageFileUtil.kt | 29 +++++++---- 8 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt create mode 100644 domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt create mode 100644 domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt create mode 100644 external/src/main/java/com/foke/together/external/repository/ImageRepository.kt diff --git a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt index 4b6a461..cb72323 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt @@ -1,20 +1,24 @@ package com.foke.together.domain.interactor +import android.content.Context import com.foke.together.domain.output.ExternalCameraRepositoryInterface +import com.foke.together.domain.output.ImageRepositoryInterface import com.foke.together.util.AppLog +import com.foke.together.util.ImageFileUtil +import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject class CaptureWithExternalCameraUseCase @Inject constructor( - private val externalCameraRepository: ExternalCameraRepositoryInterface + @ApplicationContext private val context: Context, + private val externalCameraRepository: ExternalCameraRepositoryInterface, + private val imageRepository: ImageRepositoryInterface ) { - suspend operator fun invoke(): Result { + suspend operator fun invoke(fileName: String): Result { externalCameraRepository.capture() .onSuccess { // TODO: save Bitmap to internal storage AppLog.i(TAG, "capture", "success: $it") - - // TODO: implement file save code here ... - + imageRepository.saveExternal(it, fileName) return Result.success(Unit) } .onFailure { diff --git a/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt new file mode 100644 index 0000000..54ebfa2 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt @@ -0,0 +1,22 @@ +package com.foke.together.domain.interactor + +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.domain.output.ImageRepositoryInterface +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GeneratePhotoFrameUseCase @Inject constructor( + @ApplicationContext private val context: Context, + private val imageRepositoryInterface: ImageRepositoryInterface +){ + fun getCutFrameType(type: Int): Flow = imageRepositoryInterface.getCutFrameType(type) + suspend fun setCutFrameType(type: Int) = imageRepositoryInterface.setCutFrameType(type) + + fun getCapturedImageListUri(): List = imageRepositoryInterface.getPkgInternalUriList() + suspend fun clearCapturedImageList() = imageRepositoryInterface.clearPkgInternal() + suspend fun saveFinalImage(image: Bitmap, fileName: String) = imageRepositoryInterface.saveExternal(image, fileName) +} \ 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 new file mode 100644 index 0000000..c13693b --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/entity/CutFrameType.kt @@ -0,0 +1,26 @@ +package com.foke.together.domain.interactor.entity + +enum class CutFrameType { + MAKER_FAIRE, + FOURCUT_LIGHT, + FOURCUT_DARK; + + companion object { + fun findBy(name: String): CutFrameType { + return when (name) { + MAKER_FAIRE.name -> MAKER_FAIRE + FOURCUT_LIGHT.name -> FOURCUT_LIGHT + FOURCUT_DARK.name -> FOURCUT_DARK + else -> throw IllegalArgumentException("Unknown value: $name") + } + } + fun findBy(ordinal: Int): CutFrameType { + return when (ordinal) { + MAKER_FAIRE.ordinal -> MAKER_FAIRE + FOURCUT_LIGHT.ordinal -> FOURCUT_LIGHT + FOURCUT_DARK.ordinal -> FOURCUT_DARK + else -> throw IllegalArgumentException("Unknown value: $ordinal") + } + } + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt new file mode 100644 index 0000000..1c66d5d --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt @@ -0,0 +1,20 @@ +package com.foke.together.domain.output + +import android.graphics.Bitmap +import android.net.Uri +import android.os.Environment +import com.foke.together.domain.interactor.entity.CutFrameType +import kotlinx.coroutines.flow.Flow + +interface ImageRepositoryInterface { + fun getCutFrameType(type: Int): Flow + suspend fun setCutFrameType(type: Int) + // 촬영한 사진들 모음 + suspend fun savePkgInternal(image: Bitmap, fileName: String) : Uri + fun getPkgInternalUriList() : List + suspend fun clearPkgInternal() + + // 완성된 프레임 모음 + suspend fun saveExternal(image: Bitmap, fileName :String) : Uri + fun getUriToBitmap(imageUri: Uri): Bitmap +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt new file mode 100644 index 0000000..d06424a --- /dev/null +++ b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt @@ -0,0 +1,52 @@ +package com.foke.together.external.repository + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.net.Uri +import android.provider.MediaStore +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.domain.output.ImageRepositoryInterface +import com.foke.together.util.ImageFileUtil +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class ImageRepository @Inject constructor( + @ApplicationContext private val context: Context +): ImageRepositoryInterface{ + private val cutFrameTypeFlow: MutableStateFlow = MutableStateFlow(CutFrameType.MAKER_FAIRE) + + override fun getCutFrameType(type: Int): Flow = cutFrameTypeFlow + override suspend fun setCutFrameType(type: Int) { + cutFrameTypeFlow.emit(CutFrameType.findBy(type)) + } + + override suspend fun savePkgInternal(image: Bitmap, fileName: String): Uri { + return ImageFileUtil.saveBitmapInternal(context, image, fileName) + } + + override fun getPkgInternalUriList(): List { + return context.filesDir.listFiles()?.map { + Uri.fromFile(it) + } ?: emptyList() + } + + override suspend fun clearPkgInternal() { + context.filesDir.listFiles()?.forEach { + it.delete() + } + } + + override suspend fun saveExternal(image: Bitmap, fileName: String): Uri { + return ImageFileUtil.saveBitmapMedia(context, image, fileName) + } + + override fun getUriToBitmap(imageUri: Uri): Bitmap { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, imageUri)) + } + +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt b/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt index 50f3829..90b3f7d 100644 --- a/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt +++ b/external/src/main/java/com/foke/together/external/repository/di/RepositoryModule.kt @@ -1,7 +1,9 @@ package com.foke.together.external.repository.di import com.foke.together.domain.output.ExternalCameraRepositoryInterface +import com.foke.together.domain.output.ImageRepositoryInterface import com.foke.together.external.repository.ExternalCameraRepository +import com.foke.together.external.repository.ImageRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,4 +16,9 @@ abstract class RepositoryModule { abstract fun bindAppPreferenceRepository( externalCameraRepository: ExternalCameraRepository ): ExternalCameraRepositoryInterface + + @Binds + abstract fun bindImageRepository( + imageRepository: ImageRepository + ): ImageRepositoryInterface } \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt index 1bf4689..e10c929 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt @@ -37,7 +37,7 @@ class CameraViewModel @Inject constructor( } override fun onFinish() { viewModelScope.launch { - captureWithExternalCameraUseCase() + captureWithExternalCameraUseCase("capture_${_captureCount.value}") _progressState.floatValue = 1f if (_captureCount.intValue < AppPolicy.CAPTURE_COUNT) { _captureCount.intValue += 1 diff --git a/util/src/main/java/com/foke/together/util/ImageFileUtil.kt b/util/src/main/java/com/foke/together/util/ImageFileUtil.kt index 1e1f1c2..de2320c 100644 --- a/util/src/main/java/com/foke/together/util/ImageFileUtil.kt +++ b/util/src/main/java/com/foke/together/util/ImageFileUtil.kt @@ -6,6 +6,7 @@ import android.content.Intent.createChooser import android.graphics.Bitmap import android.media.MediaScannerConnection import android.net.Uri +import android.os.Environment import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.core.content.ContextCompat.startActivity @@ -16,28 +17,25 @@ import kotlin.coroutines.resume object ImageFileUtil { - suspend fun saveGraphicsLayer( + suspend fun saveBitmapInternal( context: Context, - graphicsLayer: GraphicsLayer, + bitmap: Bitmap, fileName: String ): Uri { - var uri : Uri = Uri.EMPTY - val bitmap = graphicsLayer.toImageBitmap() - bitmap.asAndroidBitmap().saveToDisk(context, fileName) + val uri = bitmap.saveToInternal(context, fileName) return uri } - suspend fun saveBitmap( + suspend fun saveBitmapMedia( context: Context, bitmap: Bitmap, fileName: String ): Uri { - var uri : Uri = Uri.EMPTY - bitmap.saveToDisk(context, fileName) + val uri = bitmap.saveToMedia(context, fileName) return uri } - private suspend fun Bitmap.saveToDisk(context: Context, fileName: String): Uri { + private suspend fun Bitmap.saveToInternal(context: Context, fileName: String): Uri { val file = File( context.filesDir, fileName + ".jpg" @@ -45,7 +43,18 @@ object ImageFileUtil { file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100) - return scanFilePath(context, file.path) ?: throw Exception("File could not be saved") + return Uri.fromFile(file) ?: Uri.EMPTY + } + + private suspend fun Bitmap.saveToMedia(context: Context, fileName: String): Uri { + val file = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), + fileName + ".jpg" + ) + + file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100) + + return scanFilePath(context, file.absolutePath) ?: throw Exception("File could not be saved") } private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) { From 3814ca84f745bbc3d4d5eb06184ace492d1f2e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9C=A4=EC=88=98?= Date: Wed, 9 Oct 2024 20:15:39 +0900 Subject: [PATCH 2/3] Add image generation functionality and update navigation routes --- .../CaptureWithExternalCameraUseCase.kt | 5 +- .../interactor/GeneratePhotoFrameUseCase.kt | 2 +- .../domain/output/ImageRepositoryInterface.kt | 2 +- .../external/repository/ImageRepository.kt | 6 +- gradle/libs.versions.toml | 2 + presenter/build.gradle.kts | 1 + .../together/presenter/frame/FourCutFrame.kt | 83 +++++++----- .../presenter/frame/MakerFaireFrame.kt | 52 ++++---- .../together/presenter/navigation/NavGraph.kt | 20 ++- .../together/presenter/navigation/NavRoute.kt | 1 + .../together/presenter/screen/CameraScreen.kt | 15 ++- .../presenter/screen/GenerateImageScreen.kt | 121 ++++++++++++++++++ .../presenter/screen/SelectFrameScreen.kt | 10 +- .../presenter/viewmodel/CameraViewModel.kt | 3 +- .../viewmodel/GenerateImageViewModel.kt | 36 ++++++ .../viewmodel/SelectFrameViewModel.kt | 8 +- .../java/com/foke/together/util/AppPolicy.kt | 4 +- .../java/com/foke/together/util/TimeUtil.kt | 18 ++- 18 files changed, 310 insertions(+), 79 deletions(-) create mode 100644 presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt create mode 100644 presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt diff --git a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt index cb72323..85146bd 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt @@ -18,7 +18,10 @@ class CaptureWithExternalCameraUseCase @Inject constructor( .onSuccess { // TODO: save Bitmap to internal storage AppLog.i(TAG, "capture", "success: $it") - imageRepository.saveExternal(it, fileName) +// if(it == null) { +// return Result.failure(Exception("Bitmap is null")) +// } +// imageRepository.saveExternal(it, fileName) return Result.success(Unit) } .onFailure { diff --git a/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt index 54ebfa2..d7beaf1 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt @@ -13,7 +13,7 @@ class GeneratePhotoFrameUseCase @Inject constructor( @ApplicationContext private val context: Context, private val imageRepositoryInterface: ImageRepositoryInterface ){ - fun getCutFrameType(type: Int): Flow = imageRepositoryInterface.getCutFrameType(type) + fun getCutFrameType(): CutFrameType = imageRepositoryInterface.getCutFrameType() suspend fun setCutFrameType(type: Int) = imageRepositoryInterface.setCutFrameType(type) fun getCapturedImageListUri(): List = imageRepositoryInterface.getPkgInternalUriList() diff --git a/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt index 1c66d5d..a038e96 100644 --- a/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt +++ b/domain/src/main/java/com/foke/together/domain/output/ImageRepositoryInterface.kt @@ -7,7 +7,7 @@ import com.foke.together.domain.interactor.entity.CutFrameType import kotlinx.coroutines.flow.Flow interface ImageRepositoryInterface { - fun getCutFrameType(type: Int): Flow + fun getCutFrameType(): CutFrameType suspend fun setCutFrameType(type: Int) // 촬영한 사진들 모음 suspend fun savePkgInternal(image: Bitmap, fileName: String) : Uri diff --git a/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt index d06424a..76112c7 100644 --- a/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt +++ b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt @@ -18,11 +18,11 @@ import javax.inject.Inject class ImageRepository @Inject constructor( @ApplicationContext private val context: Context ): ImageRepositoryInterface{ - private val cutFrameTypeFlow: MutableStateFlow = MutableStateFlow(CutFrameType.MAKER_FAIRE) + private var cutFrameType: CutFrameType = CutFrameType.MAKER_FAIRE - override fun getCutFrameType(type: Int): Flow = cutFrameTypeFlow + override fun getCutFrameType(): CutFrameType = cutFrameType override suspend fun setCutFrameType(type: Int) { - cutFrameTypeFlow.emit(CutFrameType.findBy(type)) + cutFrameType = CutFrameType.findBy(type) } override suspend fun savePkgInternal(image: Bitmap, fileName: String): Uri { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6aa54bd..c335ff4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,6 +65,7 @@ ui-graphics-android = "1.7.3" junit = "4.13.2" junit-version = "1.2.1" espresso-core = "3.6.1" +coilCompose = "2.7.0" @@ -128,6 +129,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit-version" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coilCompose" } diff --git a/presenter/build.gradle.kts b/presenter/build.gradle.kts index 521f97d..142fb79 100644 --- a/presenter/build.gradle.kts +++ b/presenter/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { // navigation implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.navigation.compose) + implementation(libs.coil.compose) // test testImplementation(libs.junit) diff --git a/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt b/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt index 5c1271e..3b684f9 100644 --- a/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt +++ b/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt @@ -1,52 +1,63 @@ package com.foke.together.presenter.frame +import android.net.Uri import androidx.compose.foundation.background +import androidx.compose.foundation.border 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.aspectRatio -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.theme.highContrastDarkColorScheme import com.foke.together.presenter.theme.mediumContrastLightColorScheme +import com.foke.together.util.AppPolicy import com.foke.together.util.TimeUtil @Composable fun FourCutFrame( // TODO: Need to refactoring. separate frame design with application theme designColorScheme: ColorScheme = mediumContrastLightColorScheme, - cameraImageUrlList : List? = null, + cameraImageUrlList : List? = null, ) { ConstraintLayout( modifier = Modifier .aspectRatio(ratio = 0.3333f) .background(color = designColorScheme.surface) - .padding(10.dp) + .border(1.dp, designColorScheme.inverseSurface) ) { - val (startBarrier, endBarrier, cameraColumn, background, decorateImage, curTime) = createRefs() + val (cameraColumn, decorateRow, decorateImage, curTime) = createRefs() + val logoImageGuideLineStart = createGuidelineFromStart(0.4f) + val logoImageGuideLineEnd = createGuidelineFromEnd(0.4f) + val imageLogoBarrier = createTopBarrier(cameraColumn, decorateImage) + val logoTextBarrier = createTopBarrier(decorateImage, curTime ) LazyColumn( state = rememberLazyListState(), verticalArrangement = Arrangement.spacedBy(10.dp), - contentPadding = PaddingValues(horizontal = 10.dp, vertical = 10.dp), + contentPadding = PaddingValues(horizontal = 15.dp, vertical = 15.dp), modifier = Modifier .constrainAs(cameraColumn) { - top.linkTo(parent.top, margin = 15.dp) + top.linkTo(parent.top) bottom.linkTo(decorateImage.top) start.linkTo(parent.start) end.linkTo(parent.end) @@ -54,44 +65,52 @@ fun FourCutFrame( height = Dimension.wrapContent } ) { - items(4){ + items(AppPolicy.CAPTURE_COUNT){ //TODO: add camera image // change Box -> ImageView Box( modifier = Modifier .aspectRatio(1.5f) .background(color = designColorScheme.inverseSurface) - ) + ){ + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(cameraImageUrlList?.get(it)) + .build(), + contentDescription = "", + modifier = Modifier.fillParentMaxSize() + + ) + } } } - - Icon( - imageVector = ImageVector.vectorResource( - id = R.drawable.fourcut_together - ), - contentDescription = "for decorate", + Column( modifier = Modifier - .constrainAs(decorateImage) { + .constrainAs(decorateRow){ top.linkTo(cameraColumn.bottom) + bottom.linkTo(parent.bottom) start.linkTo(parent.start) end.linkTo(parent.end) - } - .size( - width = 48.dp, - height = 48.dp + height = Dimension.fillToConstraints + width = Dimension.fillToConstraints + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = ImageVector.vectorResource( + id = R.drawable.fourcut_together ), - tint = designColorScheme.primaryContainer - ) - Text( - text = TimeUtil.getCurrentTime(), - modifier = Modifier.constrainAs(curTime){ - top.linkTo(decorateImage.bottom) - start.linkTo(parent.start) - end.linkTo(parent.end) - }, - color = designColorScheme.inverseSurface, - fontSize = 12.sp - ) + contentDescription = "for decorate", + modifier = Modifier.weight(1f), + tint = designColorScheme.primaryContainer + ) + Text( + text = TimeUtil.getCurrentDate(), + modifier = Modifier.weight(1f), + color = designColorScheme.inverseSurface, + fontSize = 12.sp + ) + } } } diff --git a/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt b/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt index 994ba73..0ac190f 100644 --- a/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt +++ b/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt @@ -1,7 +1,9 @@ package com.foke.together.presenter.frame +import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -14,19 +16,23 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme +import com.foke.together.util.AppPolicy import com.foke.together.util.TimeUtil @Composable fun MakerFaireFrame( - cameraImageUrlList : List? = null, + cameraImageUrlList : List? = null, backgroundColor : Color = Color(0xFFF5B934), decorateImageUrl: String? = null, ) { @@ -34,10 +40,11 @@ fun MakerFaireFrame( modifier = Modifier .aspectRatio(ratio = 0.3333f) .background(backgroundColor) - .padding(10.dp) + .border(1.dp, Color.White) ) { - val (startBarrier, endBarrier, cameraColumn, background, decorateImage, curTime) = createRefs() - + val (cameraColumn,decorateImage, curTime) = createRefs() + val logoImageGuideLineStart = createGuidelineFromStart(0.15f) + val logoImageGuideLineEnd = createGuidelineFromEnd(0.15f) LazyColumn( state = rememberLazyListState(), verticalArrangement = Arrangement.spacedBy(10.dp), @@ -51,23 +58,18 @@ fun MakerFaireFrame( } .wrapContentSize() ) { - items(4){ - if(backgroundColor == Color.White) { - //TODO: add camera image - // change Box -> ImageView - Box( - modifier = Modifier - .aspectRatio(1.5f) - .background(color = Color.Black) - ) - } - else { - //TODO: add camera image - // change Box -> ImageView - Box( - modifier = Modifier - .aspectRatio(1.5f) - .background(color = Color.White) + items(AppPolicy.CAPTURE_COUNT){ + Box( + modifier = Modifier + .aspectRatio(1.5f) + .background(color = Color.White) + ){ + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(cameraImageUrlList?.get(it)) + .build(), + contentDescription = "", + modifier = Modifier.fillParentMaxSize() ) } } @@ -81,15 +83,15 @@ fun MakerFaireFrame( modifier = Modifier.constrainAs(decorateImage){ top.linkTo(cameraColumn.bottom) bottom.linkTo(curTime.top) - start.linkTo(parent.start) - end.linkTo(parent.end) - height = Dimension.wrapContent + start.linkTo(logoImageGuideLineStart) + end.linkTo(logoImageGuideLineEnd) width = Dimension.fillToConstraints + height = Dimension.wrapContent } ) Text( - text = TimeUtil.getCurrentTime(), + text = TimeUtil.getCurrentDate(), modifier = Modifier.constrainAs(curTime){ top.linkTo(decorateImage.bottom) bottom.linkTo(parent.bottom, margin = 15.dp) 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 09d8d41..be278f8 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 @@ -6,6 +6,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.foke.together.presenter.screen.CameraScreen +import com.foke.together.presenter.screen.GenerateImageScreen import com.foke.together.presenter.screen.HomeScreen import com.foke.together.presenter.screen.SelectFrameScreen import com.foke.together.presenter.screen.SelectMethodScreen @@ -23,6 +24,7 @@ fun NavGraph(navController: NavHostController) { addSelectFrameScreen(navController, this) addSelectMethodScreen(navController, this) addCameraScreen(navController, this) + addGenerateImageScreen(navController, this) addShareScreen(navController, this) } } @@ -82,11 +84,27 @@ private fun addCameraScreen( ) { navGraphBuilder.composable(route = NavRoute.Camera.path) { CameraScreen( + navigateToGenerateImage = { + navController.navigate(NavRoute.GenerateImage.path) + }, + popBackStack = { + navController.popBackStack(NavRoute.Home.path, inclusive = false) + } + ) + } +} + +private fun addGenerateImageScreen( + navController: NavHostController, + navGraphBuilder: NavGraphBuilder +) { + navGraphBuilder.composable(route = NavRoute.GenerateImage.path) { + GenerateImageScreen( navigateToShare = { navController.navigate(NavRoute.Share.path) }, popBackStack = { - navController.popBackStack(NavRoute.Home.path, inclusive = false) + navController.popBackStack(NavRoute.Home.path, inclusive = false) } ) } diff --git a/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt b/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt index 440ce39..c3ede5d 100644 --- a/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt +++ b/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt @@ -7,6 +7,7 @@ sealed class NavRoute(val path: String) { object SelectFrame: NavRoute("select_frame") object SelectMethod: NavRoute("select_method") object Camera: NavRoute("camera") + object GenerateImage: NavRoute("generate_image") object Share: NavRoute("share") // build navigation path (for screen navigation) diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt index a3061f7..375db67 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt @@ -36,13 +36,14 @@ import kotlinx.coroutines.flow.map @Composable fun CameraScreen( - navigateToShare: () -> Unit, + navigateToGenerateImage: () -> Unit, popBackStack: () -> Unit, viewModel: CameraViewModel = hiltViewModel() ) { val TAG = "CameraScreen" var mjpegView: MjpegView? = null val externalCameraIP = viewModel.externalCameraIP + var frameCount = 0 ConstraintLayout( modifier = Modifier.fillMaxSize() ) { @@ -102,10 +103,13 @@ fun CameraScreen( } override fun onNewFrame(image: Bitmap?) { - AppLog.d(TAG, "onNewFrame") // stream 한장 받을때마다 오는 콜백 //TODO: 화면 로딩(30프레임 이상) 후 재실행 - viewModel.startCaptureTimer() + frameCount++ + if(frameCount > 20) { + frameCount = 0 + viewModel.startCaptureTimer() + } } override fun onError(error: MjpegViewError?) { @@ -135,7 +139,7 @@ fun CameraScreen( ) } LifecycleEventEffect(Lifecycle.Event.ON_START) { - viewModel.setCaptureTimer { navigateToShare() } + viewModel.setCaptureTimer { navigateToGenerateImage() } AppLog.d(TAG, "ON_START", mjpegView.toString()) } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { @@ -145,6 +149,7 @@ fun CameraScreen( LifecycleEventEffect(Lifecycle.Event.ON_STOP) { viewModel.stopCaptureTimer() AppLog.d(TAG, "ON_STOP", mjpegView.toString()) + frameCount = 0 mjpegView?.stopStream() } } @@ -158,7 +163,7 @@ private fun DefaultPreview() { color = MaterialTheme.colorScheme.background ) { CameraScreen( - navigateToShare = {}, + navigateToGenerateImage = {}, popBackStack = {} ) } diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt new file mode 100644 index 0000000..4146e9e --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt @@ -0,0 +1,121 @@ +package com.foke.together.presenter.screen + +import android.net.Uri +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.presenter.frame.FourCutFrame +import com.foke.together.presenter.frame.MakerFaireFrame +import com.foke.together.presenter.theme.FourCutTogetherTheme +import com.foke.together.presenter.theme.mediumContrastLightColorScheme +import com.foke.together.presenter.viewmodel.GenerateImageViewModel +import kotlinx.coroutines.flow.map + +@Composable +fun GenerateImageScreen( + navigateToShare: () -> Unit, + popBackStack: () -> Unit, + viewModel: GenerateImageViewModel = hiltViewModel() +) { + + val graphicsLayer = rememberGraphicsLayer() + + ConstraintLayout( + modifier = Modifier.fillMaxSize() + ) { + val (editFrame, description) = createRefs() + Text( + text = "이미지를 생성중입니다", + modifier = Modifier.constrainAs(description) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(editFrame.top) + }, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + ) + Row( + modifier = Modifier + .constrainAs(editFrame) { + top.linkTo(description.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + } + .aspectRatio(0.6666f) + .drawWithContent { + graphicsLayer.record { + this@drawWithContent.drawContent() + } + drawLayer(graphicsLayer) + } + ) { + GetFrame( + cutFrameType = viewModel.cutFrameType.ordinal, + imageUri = viewModel.imageUri + ) + GetFrame( + cutFrameType = viewModel.cutFrameType.ordinal, + imageUri = viewModel.imageUri + ) + } + } + + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + viewModel.generateImage(graphicsLayer).map { isCompleted -> + if (isCompleted) { + navigateToShare() + } + } + } +} + + +@Composable +fun GetFrame(cutFrameType : Int, imageUri: List): Unit{ + when(cutFrameType) { + CutFrameType.MAKER_FAIRE.ordinal -> MakerFaireFrame( + cameraImageUrlList = imageUri + ) + + CutFrameType.FOURCUT_LIGHT.ordinal -> FourCutFrame( + designColorScheme = mediumContrastLightColorScheme, + cameraImageUrlList = imageUri + ) + + CutFrameType.FOURCUT_DARK.ordinal -> FourCutFrame( + designColorScheme = mediumContrastLightColorScheme, + cameraImageUrlList = imageUri + ) + else -> TODO() + } +} + +@Preview(showBackground = true) +@Composable +private fun DefaultFrame() { + FourCutTogetherTheme { + GenerateImageScreen( + navigateToShare = {}, + popBackStack = {} + ) + } +} \ No newline at end of file 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 3997aa7..ba1c934 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 @@ -27,6 +27,7 @@ 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.CutFrameType import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.viewmodel.SelectFrameViewModel @@ -39,7 +40,7 @@ fun SelectFrameScreen( ) { FourCutTogetherTheme { val pagerState = rememberPagerState { - 3 // 총 페이지 수 설정 + CutFrameType.entries.size // 총 페이지 수 설정 } ConstraintLayout( modifier = Modifier.fillMaxSize() @@ -101,10 +102,11 @@ fun SelectFrameScreen( ) ) { page -> when(page){ - 0 -> Image(painter = painterResource(id = R.drawable.fourcut_frame_medium_light), contentDescription = "fourcut_frame_medium_light") - 1 -> Image(painter = painterResource(id = R.drawable.fourcut_frame_medium_dark), contentDescription = "fourcut_frame_medium_dark") - 2 -> Image(painter = painterResource(id = R.drawable.maker_faire_frame), contentDescription = "maker_faire_frame") + CutFrameType.FOURCUT_LIGHT.ordinal -> Image(painter = painterResource(id = R.drawable.fourcut_frame_medium_light), contentDescription = "fourcut_frame_medium_light") + CutFrameType.FOURCUT_DARK.ordinal -> Image(painter = painterResource(id = R.drawable.fourcut_frame_medium_dark), contentDescription = "fourcut_frame_medium_dark") + CutFrameType.MAKER_FAIRE.ordinal -> Image(painter = painterResource(id = R.drawable.maker_faire_frame), contentDescription = "maker_faire_frame") } + viewModel.setCutFrameType(page) } IconButton( diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt index e10c929..b13e623 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.foke.together.domain.interactor.CaptureWithExternalCameraUseCase +import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase import com.foke.together.domain.interactor.GetExternalCameraPreviewUrlUseCase import com.foke.together.util.AppPolicy import com.foke.together.util.AppPolicy.CAPTURE_INTERVAL @@ -53,8 +54,6 @@ class CameraViewModel @Inject constructor( } fun startCaptureTimer() = viewModelScope.launch{ - // TODO: Capture 후 Preview 로딩까지의 Delay 구현하기 - delay(5000) if(captureTimer != null){ if(!mTimerState){ mTimerState = true diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt new file mode 100644 index 0000000..46f1035 --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt @@ -0,0 +1,36 @@ +package com.foke.together.presenter.viewmodel + +import android.content.Context +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.util.AppLog +import com.foke.together.util.ImageFileUtil +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class GenerateImageViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase +): ViewModel() { + val cutFrameType: CutFrameType = generatePhotoFrameUseCase.getCutFrameType() + val imageUri = generatePhotoFrameUseCase.getCapturedImageListUri() + + private fun generateImageJob(graphicsLayer: GraphicsLayer)= viewModelScope.launch { + val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() + val finalImageUri = ImageFileUtil.saveBitmapInternal(context, bitmap, "finalImage") + AppLog.d("GenerateImageViewModel", "generateImageJob: finalImageUri: $finalImageUri") + } + + fun generateImage(graphicsLayer: GraphicsLayer): Flow = flow { + emit(generateImageJob(graphicsLayer).isCompleted) + } +} \ No newline at end of file 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 cfc161a..f3bca66 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 @@ -1,11 +1,17 @@ package com.foke.together.presenter.viewmodel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class SelectFrameViewModel @Inject constructor( + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase ): ViewModel() { - // TODO: add viewmodel code here + fun setCutFrameType(type:Int) = viewModelScope.launch { + generatePhotoFrameUseCase.setCutFrameType(type) + } } \ No newline at end of file diff --git a/util/src/main/java/com/foke/together/util/AppPolicy.kt b/util/src/main/java/com/foke/together/util/AppPolicy.kt index 2a53c1f..9c95cf0 100644 --- a/util/src/main/java/com/foke/together/util/AppPolicy.kt +++ b/util/src/main/java/com/foke/together/util/AppPolicy.kt @@ -3,7 +3,7 @@ package com.foke.together.util object AppPolicy { const val isDebugMode = true - const val DEFAULT_EXTERNAL_CAMERA_IP = "0.0.0.0" + const val DEFAULT_EXTERNAL_CAMERA_IP = "192.168.0.71:5000" // network const val WEB_SERVER_URL = "http://4cuts.store/" @@ -12,7 +12,7 @@ object AppPolicy { const val WEB_WRITE_TIMEOUT = 10L const val WEB_FILE_MAX_CONTENT_LENGTH = 20971520 - const val EXTERNAL_CAMERA_DEFAULT_SERVER_URL = "http://0.0.0.0" + const val EXTERNAL_CAMERA_DEFAULT_SERVER_URL = "http://192.168.0.71:5000" const val EXTERNAL_CAMERA_CONNECT_TIMEOUT = 10L const val EXTERNAL_CAMERA_READ_TIMEOUT = 10L const val EXTERNAL_CAMERA_WRITE_TIMEOUT = 10L diff --git a/util/src/main/java/com/foke/together/util/TimeUtil.kt b/util/src/main/java/com/foke/together/util/TimeUtil.kt index b697671..2fd6037 100644 --- a/util/src/main/java/com/foke/together/util/TimeUtil.kt +++ b/util/src/main/java/com/foke/together/util/TimeUtil.kt @@ -1,9 +1,25 @@ package com.foke.together.util +import java.text.SimpleDateFormat +import java.util.Date + object TimeUtil { fun getCurrentTime(): String { // TODO. Implement this function // - output format example: "2024.09.27" - return "2024.09.27" + val timeFormat = "yyyyMMddHHmmss" + val time = Date(System.currentTimeMillis()) + val simpletimeFormat = SimpleDateFormat(timeFormat) + val simpleTime: String = simpletimeFormat.format(time) + return simpleTime + } + fun getCurrentDate(): String { + // TODO. Implement this function + // - output format example: "2024.09.27" + val dateFormat = "yyyy.MM.dd" + val date = Date(System.currentTimeMillis()) + val simpleDateFormat = SimpleDateFormat(dateFormat) + val simpleDate: String = simpleDateFormat.format(date) + return simpleDate } } \ No newline at end of file From c482c73b40e6eda77d33446d551679831afdb751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9C=A4=EC=88=98?= Date: Wed, 9 Oct 2024 23:25:51 +0900 Subject: [PATCH 3/3] Generate Image UseCase --- .../CaptureWithExternalCameraUseCase.kt | 8 +- .../interactor/GeneratePhotoFrameUseCase.kt | 1 + .../external/repository/ImageRepository.kt | 17 +++- .../together/presenter/frame/FourCutFrame.kt | 20 ++-- .../presenter/frame/MakerFaireFrame.kt | 12 ++- .../together/presenter/navigation/NavGraph.kt | 30 ++++-- .../together/presenter/navigation/NavRoute.kt | 3 +- .../together/presenter/screen/CameraScreen.kt | 19 +++- .../screen/GenerateSingleRowImageScreen.kt | 96 +++++++++++++++++++ ...Screen.kt => GenerateTwoRowImageScreen.kt} | 31 +++--- .../together/presenter/screen/ShareScreen.kt | 60 +++++++----- .../presenter/viewmodel/CameraViewModel.kt | 17 +++- ....kt => GenerateSingleRowImageViewModel.kt} | 18 +--- .../viewmodel/GenerateTwoRowImageViewModel.kt | 28 ++++++ .../presenter/viewmodel/ShareViewModel.kt | 11 ++- 15 files changed, 291 insertions(+), 80 deletions(-) create mode 100644 presenter/src/main/java/com/foke/together/presenter/screen/GenerateSingleRowImageScreen.kt rename presenter/src/main/java/com/foke/together/presenter/screen/{GenerateImageScreen.kt => GenerateTwoRowImageScreen.kt} (82%) rename presenter/src/main/java/com/foke/together/presenter/viewmodel/{GenerateImageViewModel.kt => GenerateSingleRowImageViewModel.kt} (58%) create mode 100644 presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateTwoRowImageViewModel.kt diff --git a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt index 85146bd..28827e7 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/CaptureWithExternalCameraUseCase.kt @@ -18,10 +18,10 @@ class CaptureWithExternalCameraUseCase @Inject constructor( .onSuccess { // TODO: save Bitmap to internal storage AppLog.i(TAG, "capture", "success: $it") -// if(it == null) { -// return Result.failure(Exception("Bitmap is null")) -// } -// imageRepository.saveExternal(it, fileName) + if(it == null) { + return Result.failure(Exception("Bitmap is null")) + } + imageRepository.saveExternal(it, fileName) return Result.success(Unit) } .onFailure { diff --git a/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt index d7beaf1..446cdc1 100644 --- a/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt +++ b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt @@ -18,5 +18,6 @@ class GeneratePhotoFrameUseCase @Inject constructor( fun getCapturedImageListUri(): List = imageRepositoryInterface.getPkgInternalUriList() suspend fun clearCapturedImageList() = imageRepositoryInterface.clearPkgInternal() + suspend fun saveGraphicsLayerImage(image: Bitmap, fileName: String) = imageRepositoryInterface.savePkgInternal(image, fileName) suspend fun saveFinalImage(image: Bitmap, fileName: String) = imageRepositoryInterface.saveExternal(image, fileName) } \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt index 76112c7..6c21c63 100644 --- a/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt +++ b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt @@ -30,14 +30,21 @@ class ImageRepository @Inject constructor( } override fun getPkgInternalUriList(): List { - return context.filesDir.listFiles()?.map { - Uri.fromFile(it) - } ?: emptyList() + var uriList = mutableListOf() + context.filesDir.listFiles().forEach { + if(it.name.contains("capture")){ + // capture로 시작하는 파일만 반환 + uriList.add(Uri.fromFile(it)) + } + } + return uriList } override suspend fun clearPkgInternal() { - context.filesDir.listFiles()?.forEach { - it.delete() + context.filesDir.listFiles().forEach { + if(it.name.contains(".jpg")){ + it.delete() + } } } diff --git a/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt b/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt index 3b684f9..49f633e 100644 --- a/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt +++ b/presenter/src/main/java/com/foke/together/presenter/frame/FourCutFrame.kt @@ -15,8 +15,14 @@ import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource @@ -25,6 +31,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import coil.compose.AsyncImage import coil.request.ImageRequest import com.foke.together.presenter.R @@ -32,13 +40,15 @@ import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.theme.highContrastDarkColorScheme import com.foke.together.presenter.theme.mediumContrastLightColorScheme import com.foke.together.util.AppPolicy +import com.foke.together.util.ImageFileUtil import com.foke.together.util.TimeUtil +import kotlinx.coroutines.launch @Composable fun FourCutFrame( // TODO: Need to refactoring. separate frame design with application theme designColorScheme: ColorScheme = mediumContrastLightColorScheme, - cameraImageUrlList : List? = null, + cameraImageUrlList : List? = null ) { ConstraintLayout( modifier = Modifier @@ -46,11 +56,7 @@ fun FourCutFrame( .background(color = designColorScheme.surface) .border(1.dp, designColorScheme.inverseSurface) ) { - val (cameraColumn, decorateRow, decorateImage, curTime) = createRefs() - val logoImageGuideLineStart = createGuidelineFromStart(0.4f) - val logoImageGuideLineEnd = createGuidelineFromEnd(0.4f) - val imageLogoBarrier = createTopBarrier(cameraColumn, decorateImage) - val logoTextBarrier = createTopBarrier(decorateImage, curTime ) + val (cameraColumn, decorateRow) = createRefs() LazyColumn( state = rememberLazyListState(), verticalArrangement = Arrangement.spacedBy(10.dp), @@ -58,7 +64,7 @@ fun FourCutFrame( modifier = Modifier .constrainAs(cameraColumn) { top.linkTo(parent.top) - bottom.linkTo(decorateImage.top) + bottom.linkTo(decorateRow.top) start.linkTo(parent.start) end.linkTo(parent.end) width = Dimension.fillToConstraints diff --git a/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt b/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt index 0ac190f..3e4f2d6 100644 --- a/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt +++ b/presenter/src/main/java/com/foke/together/presenter/frame/MakerFaireFrame.kt @@ -14,8 +14,14 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -23,18 +29,22 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import coil.compose.AsyncImage import coil.request.ImageRequest import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.util.AppPolicy +import com.foke.together.util.ImageFileUtil import com.foke.together.util.TimeUtil +import kotlinx.coroutines.launch @Composable fun MakerFaireFrame( cameraImageUrlList : List? = null, backgroundColor : Color = Color(0xFFF5B934), - decorateImageUrl: String? = null, + decorateImageUrl: String? = null ) { ConstraintLayout( modifier = Modifier 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 be278f8..59b2955 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 @@ -6,7 +6,8 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.foke.together.presenter.screen.CameraScreen -import com.foke.together.presenter.screen.GenerateImageScreen +import com.foke.together.presenter.screen.GenerateSingleRowImageScreen +import com.foke.together.presenter.screen.GenerateTwoRowImageScreen import com.foke.together.presenter.screen.HomeScreen import com.foke.together.presenter.screen.SelectFrameScreen import com.foke.together.presenter.screen.SelectMethodScreen @@ -24,7 +25,8 @@ fun NavGraph(navController: NavHostController) { addSelectFrameScreen(navController, this) addSelectMethodScreen(navController, this) addCameraScreen(navController, this) - addGenerateImageScreen(navController, this) + addGenerateSingleRowImageScreen(navController, this) + addGenerateTwoRowImageScreen(navController, this) addShareScreen(navController, this) } } @@ -85,7 +87,7 @@ private fun addCameraScreen( navGraphBuilder.composable(route = NavRoute.Camera.path) { CameraScreen( navigateToGenerateImage = { - navController.navigate(NavRoute.GenerateImage.path) + navController.navigate(NavRoute.GenerateSingleRowImage.path) }, popBackStack = { navController.popBackStack(NavRoute.Home.path, inclusive = false) @@ -94,12 +96,28 @@ private fun addCameraScreen( } } -private fun addGenerateImageScreen( +private fun addGenerateSingleRowImageScreen( navController: NavHostController, navGraphBuilder: NavGraphBuilder ) { - navGraphBuilder.composable(route = NavRoute.GenerateImage.path) { - GenerateImageScreen( + navGraphBuilder.composable(route = NavRoute.GenerateSingleRowImage.path) { + GenerateSingleRowImageScreen( + navigateToTwoRow = { + navController.navigate(NavRoute.GenerateTwoRowImage.path) + }, + popBackStack = { + navController.popBackStack(NavRoute.Home.path, inclusive = false) + } + ) + } +} + +private fun addGenerateTwoRowImageScreen( + navController: NavHostController, + navGraphBuilder: NavGraphBuilder +) { + navGraphBuilder.composable(route = NavRoute.GenerateTwoRowImage.path) { + GenerateTwoRowImageScreen( navigateToShare = { navController.navigate(NavRoute.Share.path) }, diff --git a/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt b/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt index c3ede5d..e0aee63 100644 --- a/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt +++ b/presenter/src/main/java/com/foke/together/presenter/navigation/NavRoute.kt @@ -7,7 +7,8 @@ sealed class NavRoute(val path: String) { object SelectFrame: NavRoute("select_frame") object SelectMethod: NavRoute("select_method") object Camera: NavRoute("camera") - object GenerateImage: NavRoute("generate_image") + object GenerateSingleRowImage: NavRoute("generate_single_row_image") + object GenerateTwoRowImage: NavRoute("generate_two_row_image") object Share: NavRoute("share") // build navigation path (for screen navigation) diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt index 375db67..8afa406 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/CameraScreen.kt @@ -14,6 +14,9 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -44,6 +47,7 @@ fun CameraScreen( var mjpegView: MjpegView? = null val externalCameraIP = viewModel.externalCameraIP var frameCount = 0 + val graphicsLayer = rememberGraphicsLayer() ConstraintLayout( modifier = Modifier.fillMaxSize() ) { @@ -79,7 +83,16 @@ fun CameraScreen( end.linkTo(parent.end, margin = 24.dp) bottom.linkTo(imageCount.top) } - .aspectRatio(1.5f), + .aspectRatio(1.5f) + .drawWithContent { + // call record to capture the content in the graphics layer + graphicsLayer.record { + // draw the contents of the composable into the graphics layer + this@drawWithContent.drawContent() + } + // draw the graphics layer on the visible canvas + drawLayer(graphicsLayer) + }, factory = { context -> MjpegView(context).apply { mode = MjpegView.MODE_BEST_FIT @@ -106,7 +119,7 @@ fun CameraScreen( // stream 한장 받을때마다 오는 콜백 //TODO: 화면 로딩(30프레임 이상) 후 재실행 frameCount++ - if(frameCount > 20) { + if(frameCount > 30) { frameCount = 0 viewModel.startCaptureTimer() } @@ -139,7 +152,7 @@ fun CameraScreen( ) } LifecycleEventEffect(Lifecycle.Event.ON_START) { - viewModel.setCaptureTimer { navigateToGenerateImage() } + viewModel.setCaptureTimer(graphicsLayer) { navigateToGenerateImage() } AppLog.d(TAG, "ON_START", mjpegView.toString()) } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/GenerateSingleRowImageScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateSingleRowImageScreen.kt new file mode 100644 index 0000000..1274ac3 --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateSingleRowImageScreen.kt @@ -0,0 +1,96 @@ +package com.foke.together.presenter.screen + +import android.net.Uri +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.presenter.frame.FourCutFrame +import com.foke.together.presenter.frame.MakerFaireFrame +import com.foke.together.presenter.theme.FourCutTogetherTheme +import com.foke.together.presenter.theme.mediumContrastLightColorScheme +import com.foke.together.presenter.viewmodel.GenerateSingleRowImageViewModel +import com.foke.together.util.AppLog +import kotlinx.coroutines.launch + +@Composable +fun GenerateSingleRowImageScreen( + navigateToTwoRow: () -> Unit, + popBackStack: () -> Unit, + viewModel: GenerateSingleRowImageViewModel = hiltViewModel() +) { + + val graphicsLayer = rememberGraphicsLayer() + val coroutineScope = rememberCoroutineScope() + ConstraintLayout( + modifier = Modifier.fillMaxSize() + ) { + val (editFrame, description) = createRefs() + Text( + text = "이미지를 생성중입니다", + modifier = Modifier.constrainAs(description) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(editFrame.top) + }, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + ) + Row( + modifier = Modifier + .constrainAs(editFrame) { + top.linkTo(description.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + } + .aspectRatio(0.3333f) + .drawWithContent { + graphicsLayer.record { + this@drawWithContent.drawContent() + } + drawLayer(graphicsLayer) + } + ) { + GetFrame( + cutFrameType = viewModel.cutFrameType.ordinal, + imageUri = viewModel.imageUri + ) + } + } + + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + AppLog.d("GenerateSingleRowImageScreen", "LifecycleEventEffect: ON_RESUME", "${viewModel.imageUri}") + coroutineScope.launch { + viewModel.generateImage(graphicsLayer) + } + navigateToTwoRow() + } +} + +@Preview(showBackground = true) +@Composable +private fun DefaultFrame() { + FourCutTogetherTheme { + GenerateSingleRowImageScreen( + navigateToTwoRow = {}, + popBackStack = {} + ) + } +} \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt similarity index 82% rename from presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt rename to presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt index 4146e9e..53098ba 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/GenerateImageScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt @@ -6,11 +6,10 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.text.font.FontWeight @@ -25,18 +24,19 @@ import com.foke.together.presenter.frame.FourCutFrame import com.foke.together.presenter.frame.MakerFaireFrame import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.theme.mediumContrastLightColorScheme -import com.foke.together.presenter.viewmodel.GenerateImageViewModel -import kotlinx.coroutines.flow.map +import com.foke.together.presenter.viewmodel.GenerateTwoRowImageViewModel +import com.foke.together.util.AppLog +import kotlinx.coroutines.launch @Composable -fun GenerateImageScreen( +fun GenerateTwoRowImageScreen( navigateToShare: () -> Unit, popBackStack: () -> Unit, - viewModel: GenerateImageViewModel = hiltViewModel() + viewModel: GenerateTwoRowImageViewModel = hiltViewModel() ) { val graphicsLayer = rememberGraphicsLayer() - + val coroutineScope = rememberCoroutineScope() ConstraintLayout( modifier = Modifier.fillMaxSize() ) { @@ -80,17 +80,20 @@ fun GenerateImageScreen( } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { - viewModel.generateImage(graphicsLayer).map { isCompleted -> - if (isCompleted) { - navigateToShare() - } + AppLog.d("GenerateTwoRowImageScreen", "LifecycleEventEffect: ON_RESUME", "${viewModel.imageUri}") + coroutineScope.launch { + viewModel.generateImage(graphicsLayer) } + navigateToShare() } } @Composable -fun GetFrame(cutFrameType : Int, imageUri: List): Unit{ +fun GetFrame( + cutFrameType : Int, + imageUri: List +): Unit{ when(cutFrameType) { CutFrameType.MAKER_FAIRE.ordinal -> MakerFaireFrame( cameraImageUrlList = imageUri @@ -113,7 +116,7 @@ fun GetFrame(cutFrameType : Int, imageUri: List): Unit{ @Composable private fun DefaultFrame() { FourCutTogetherTheme { - GenerateImageScreen( + GenerateTwoRowImageScreen( navigateToShare = {}, popBackStack = {} ) 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 73df5e4..5a80d0c 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 @@ -1,7 +1,10 @@ package com.foke.together.presenter.screen +import android.net.Uri +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Home @@ -10,6 +13,7 @@ import androidx.compose.material.icons.filled.Share import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ChainStyle @@ -19,6 +23,10 @@ import com.foke.together.presenter.frame.FourCutFrame import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.theme.highContrastDarkColorScheme import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.foke.together.presenter.viewmodel.ShareViewModel @Composable @@ -26,42 +34,42 @@ fun ShareScreen( popBackStack: () -> Unit, viewModel: ShareViewModel = hiltViewModel() ) { + var finalSingleImageUri: Uri = Uri.EMPTY + val context = LocalContext.current + context.filesDir.listFiles().forEach { + file -> if (file.name.contains("final_single_row.jpg")) { finalSingleImageUri = Uri.fromFile(file) } + } ConstraintLayout( modifier = Modifier.fillMaxSize() ) { val (finalPic, printButton, shareButton, downloadButton, homeButton ) = createRefs() - val frameType = 0 val topGuideLine = createGuidelineFromTop(0.1f) val bottomGuideLine = createGuidelineFromBottom(0.1f) val startGuideLine = createGuidelineFromStart(0.2f) val endGuideLine = createGuidelineFromEnd(0.2f) val frameBarrier = createEndBarrier(finalPic) - createVerticalChain( homeButton, printButton, shareButton, downloadButton, chainStyle = ChainStyle.Spread ) // TODO: need check to change single ImageView - when(frameType){ - 0 -> { - Card( - modifier = Modifier - .constrainAs(finalPic){ - top.linkTo(topGuideLine) - bottom.linkTo(bottomGuideLine) - start.linkTo(parent.start, margin = 30.dp) - width = Dimension.wrapContent - height = Dimension.fillToConstraints - } - ){ - FourCutFrame( - designColorScheme = highContrastDarkColorScheme, - ) + AsyncImage( + model = ImageRequest.Builder(context) + .data(finalSingleImageUri) + .build(), + contentDescription = "", + modifier = Modifier + .constrainAs(finalPic) { + top.linkTo(topGuideLine) + bottom.linkTo(bottomGuideLine) + start.linkTo(parent.start, margin = 30.dp) + width = Dimension.wrapContent + height = Dimension.fillToConstraints } - } - } + .aspectRatio(0.3333f) + ) IconButton( onClick = { popBackStack() }, @@ -82,7 +90,7 @@ fun ShareScreen( } IconButton( - onClick = {}, + onClick = { viewModel.printImage() }, modifier = Modifier.constrainAs(printButton) { top.linkTo(homeButton.bottom) end.linkTo(parent.end, margin = 30.dp) @@ -100,7 +108,7 @@ fun ShareScreen( } IconButton( - onClick = {}, + onClick = {viewModel.shareImage()}, modifier = Modifier.constrainAs(shareButton) { top.linkTo(printButton.bottom) end.linkTo(parent.end, margin = 30.dp) @@ -118,7 +126,7 @@ fun ShareScreen( } IconButton( - onClick = {}, + onClick = { viewModel.downloadImage() }, modifier = Modifier.constrainAs(downloadButton) { top.linkTo(shareButton.bottom) end.linkTo(parent.end, margin = 30.dp) @@ -135,6 +143,14 @@ fun ShareScreen( ) } } + // TODO: Lifecycle 맞춰서 로딩하기 +// LifecycleEventEffect(Lifecycle.Event.ON_CREATE) { +// context.filesDir.listFiles().forEach { +// file -> if (file.name.contains("final_single_row.jpg")) { +// finalSingleImageUri = Uri.fromFile(file) +// } +// } +// } } @Preview(showBackground = true) diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt index b13e623..57ecd68 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/CameraViewModel.kt @@ -4,6 +4,8 @@ import android.os.CountDownTimer import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.foke.together.domain.interactor.CaptureWithExternalCameraUseCase @@ -20,7 +22,8 @@ import javax.inject.Inject @HiltViewModel class CameraViewModel @Inject constructor( getExternalCameraPreviewUrlUseCase: GetExternalCameraPreviewUrlUseCase, - private val captureWithExternalCameraUseCase: CaptureWithExternalCameraUseCase +// private val captureWithExternalCameraUseCase: CaptureWithExternalCameraUseCase, + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase ): ViewModel() { val externalCameraIP = getExternalCameraPreviewUrlUseCase() @@ -31,14 +34,21 @@ class CameraViewModel @Inject constructor( val captureCount: Int by _captureCount private var captureTimer: CountDownTimer? = null private var mTimerState = false - fun setCaptureTimer(nextNavigate: () -> Unit) { + fun setCaptureTimer( + graphicsLayer: GraphicsLayer, + nextNavigate: () -> Unit + ) { + viewModelScope.launch { + generatePhotoFrameUseCase.clearCapturedImageList() + } captureTimer = object : CountDownTimer(CAPTURE_INTERVAL, COUNTDOWN_INTERVAL) { override fun onTick(millisUntilFinished: Long) { _progressState.floatValue = 1f - (millisUntilFinished.toFloat() / CAPTURE_INTERVAL) } override fun onFinish() { viewModelScope.launch { - captureWithExternalCameraUseCase("capture_${_captureCount.value}") + val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() + generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, "capture_${_captureCount.value}") _progressState.floatValue = 1f if (_captureCount.intValue < AppPolicy.CAPTURE_COUNT) { _captureCount.intValue += 1 @@ -46,6 +56,7 @@ class CameraViewModel @Inject constructor( } else { stopCaptureTimer() _captureCount.intValue = 1 + delay(CAPTURE_INTERVAL) nextNavigate() } } diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt similarity index 58% rename from presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt rename to presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt index 46f1035..b55a793 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateImageViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt @@ -4,33 +4,25 @@ import android.content.Context import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase import com.foke.together.domain.interactor.entity.CutFrameType import com.foke.together.util.AppLog -import com.foke.together.util.ImageFileUtil import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class GenerateImageViewModel @Inject constructor( +class GenerateSingleRowImageViewModel @Inject constructor( @ApplicationContext private val context: Context, private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase ): ViewModel() { val cutFrameType: CutFrameType = generatePhotoFrameUseCase.getCutFrameType() val imageUri = generatePhotoFrameUseCase.getCapturedImageListUri() - private fun generateImageJob(graphicsLayer: GraphicsLayer)= viewModelScope.launch { + suspend fun generateImage(graphicsLayer: GraphicsLayer) { val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() - val finalImageUri = ImageFileUtil.saveBitmapInternal(context, bitmap, "finalImage") - AppLog.d("GenerateImageViewModel", "generateImageJob: finalImageUri: $finalImageUri") - } - - fun generateImage(graphicsLayer: GraphicsLayer): Flow = flow { - emit(generateImageJob(graphicsLayer).isCompleted) + val finalExternalImageUri = generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, "final_single_row") + val finalInternalImageUri = generatePhotoFrameUseCase.saveFinalImage(bitmap, "final_single_row") + AppLog.d("GenerateImageViewModel", "generateTwoRowImage" ,"twoRow: $finalExternalImageUri") } } \ No newline at end of file diff --git a/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateTwoRowImageViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateTwoRowImageViewModel.kt new file mode 100644 index 0000000..46ac53d --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateTwoRowImageViewModel.kt @@ -0,0 +1,28 @@ +package com.foke.together.presenter.viewmodel + +import android.content.Context +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.lifecycle.ViewModel +import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase +import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.util.AppLog +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +@HiltViewModel +class GenerateTwoRowImageViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase +): ViewModel() { + val cutFrameType: CutFrameType = generatePhotoFrameUseCase.getCutFrameType() + val imageUri = generatePhotoFrameUseCase.getCapturedImageListUri() + + suspend fun generateImage(graphicsLayer: GraphicsLayer) { + val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() + val finalExternalImageUri = generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, "final_two_row") + val finalInternalImageUri = generatePhotoFrameUseCase.saveFinalImage(bitmap, "final_two_row") + AppLog.d("GenerateImageViewModel", "generateTwoRowImage" ,"twoRow: $finalExternalImageUri") + } +} \ No newline at end of file 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 575622b..38332a8 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 @@ -7,5 +7,14 @@ import javax.inject.Inject @HiltViewModel class ShareViewModel @Inject constructor( ): ViewModel() { - // TODO: add viewmodel code here + fun shareImage() { + + } + fun printImage() { + + } + + fun downloadImage() { + + } } \ No newline at end of file