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 c5dbec1..4fc4b81 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 @@ -2,6 +2,7 @@ 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 com.foke.together.util.TimeUtil @@ -10,13 +11,18 @@ import javax.inject.Inject class CaptureWithExternalCameraUseCase @Inject constructor( @ApplicationContext private val context: Context, - private val externalCameraRepository: ExternalCameraRepositoryInterface + private val externalCameraRepository: ExternalCameraRepositoryInterface, + private val imageRepository: ImageRepositoryInterface ) { - suspend operator fun invoke(filepath: String): Result { + suspend operator fun invoke(fileName: String): Result { externalCameraRepository.capture() .onSuccess { - // TODO: create file module in phase4 - ImageFileUtil.saveBitmap(context, it, filepath, TimeUtil.getCurrentTimeMillis()) + // TODO: save Bitmap to internal storage + AppLog.i(TAG, "capture", "success: $it") + if(it == null) { + return Result.failure(Exception("Bitmap is null")) + } + imageRepository.saveToStorage(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..a36f94d --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/GeneratePhotoFrameUseCase.kt @@ -0,0 +1,23 @@ +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(): CutFrameType = imageRepositoryInterface.getCutFrameType() + suspend fun setCutFrameType(type: Int) = imageRepositoryInterface.setCutFrameType(type) + + fun getCapturedImageListUri(): List = imageRepositoryInterface.getCachedImageUriList() + suspend fun clearCapturedImageList() = imageRepositoryInterface.clearCacheDir() + suspend fun saveGraphicsLayerImage(image: Bitmap, fileName: String) = imageRepositoryInterface.cachingImage(image, fileName) + suspend fun saveFinalImage(image: Bitmap, fileName: String) = imageRepositoryInterface.saveToStorage(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..bad7154 --- /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(): CutFrameType + suspend fun setCutFrameType(type: Int) + // 촬영한 사진들 모음 + suspend fun cachingImage(image: Bitmap, fileName: String) : Uri + fun getCachedImageUriList() : List + suspend fun clearCacheDir() + + // 완성된 프레임 모음 + suspend fun saveToStorage(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..e9e264d --- /dev/null +++ b/external/src/main/java/com/foke/together/external/repository/ImageRepository.kt @@ -0,0 +1,59 @@ +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 var cutFrameType: CutFrameType = CutFrameType.MAKER_FAIRE + + override fun getCutFrameType(): CutFrameType = cutFrameType + override suspend fun setCutFrameType(type: Int) { + cutFrameType = CutFrameType.findBy(type) + } + + override suspend fun cachingImage(image: Bitmap, fileName: String): Uri { + return ImageFileUtil.cacheBitmap(context, image, fileName) + } + + override fun getCachedImageUriList(): List { + var uriList = mutableListOf() + context.cacheDir.listFiles().forEach { + if(it.name.contains("capture")){ + // capture로 시작하는 파일만 반환 + uriList.add(Uri.fromFile(it)) + } + } + return uriList + } + + override suspend fun clearCacheDir() { + context.cacheDir.listFiles().forEach { + if(it.name.contains(".jpg")){ + it.delete() + } + } + } + + override suspend fun saveToStorage(image: Bitmap, fileName: String): Uri { + return ImageFileUtil.saveBitmapToStorage(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 c54bcc8..9285e2d 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 @@ -16,4 +18,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/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 2aecc57..0f95372 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,97 +1,122 @@ 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.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 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 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.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 .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) = createRefs() 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) - bottom.linkTo(decorateImage.top) + top.linkTo(parent.top) + bottom.linkTo(decorateRow.top) start.linkTo(parent.start) end.linkTo(parent.end) width = Dimension.fillToConstraints 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.getCurrentDisplayTime(), - 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.getCurrentDisplayTime(), + 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 613ea50..5c01e4a 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 @@ -12,32 +14,47 @@ 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 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, + cameraImageUrlList : List? = null, backgroundColor : Color = Color(0xFFF5B934), - decorateImageUrl: String? = null, + decorateImageUrl: String? = null ) { ConstraintLayout( 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 +68,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,10 +93,10 @@ 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 } ) 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..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,6 +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.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 @@ -23,6 +25,8 @@ fun NavGraph(navController: NavHostController) { addSelectFrameScreen(navController, this) addSelectMethodScreen(navController, this) addCameraScreen(navController, this) + addGenerateSingleRowImageScreen(navController, this) + addGenerateTwoRowImageScreen(navController, this) addShareScreen(navController, this) } } @@ -82,11 +86,43 @@ private fun addCameraScreen( ) { navGraphBuilder.composable(route = NavRoute.Camera.path) { CameraScreen( + navigateToGenerateImage = { + navController.navigate(NavRoute.GenerateSingleRowImage.path) + }, + popBackStack = { + navController.popBackStack(NavRoute.Home.path, inclusive = false) + } + ) + } +} + +private fun addGenerateSingleRowImageScreen( + navController: NavHostController, + navGraphBuilder: NavGraphBuilder +) { + 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) }, 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..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,6 +7,8 @@ sealed class NavRoute(val path: String) { object SelectFrame: NavRoute("select_frame") object SelectMethod: NavRoute("select_method") object Camera: NavRoute("camera") + 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 a3061f7..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 @@ -36,13 +39,15 @@ 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 + val graphicsLayer = rememberGraphicsLayer() ConstraintLayout( modifier = Modifier.fillMaxSize() ) { @@ -78,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 @@ -102,10 +116,13 @@ fun CameraScreen( } override fun onNewFrame(image: Bitmap?) { - AppLog.d(TAG, "onNewFrame") // stream 한장 받을때마다 오는 콜백 //TODO: 화면 로딩(30프레임 이상) 후 재실행 - viewModel.startCaptureTimer() + frameCount++ + if(frameCount > 30) { + frameCount = 0 + viewModel.startCaptureTimer() + } } override fun onError(error: MjpegViewError?) { @@ -135,7 +152,7 @@ fun CameraScreen( ) } LifecycleEventEffect(Lifecycle.Event.ON_START) { - viewModel.setCaptureTimer { navigateToShare() } + viewModel.setCaptureTimer(graphicsLayer) { navigateToGenerateImage() } AppLog.d(TAG, "ON_START", mjpegView.toString()) } LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { @@ -145,6 +162,7 @@ fun CameraScreen( LifecycleEventEffect(Lifecycle.Event.ON_STOP) { viewModel.stopCaptureTimer() AppLog.d(TAG, "ON_STOP", mjpegView.toString()) + frameCount = 0 mjpegView?.stopStream() } } @@ -158,7 +176,7 @@ private fun DefaultPreview() { color = MaterialTheme.colorScheme.background ) { CameraScreen( - navigateToShare = {}, + navigateToGenerateImage = {}, popBackStack = {} ) } 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/GenerateTwoRowImageScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt new file mode 100644 index 0000000..53098ba --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt @@ -0,0 +1,124 @@ +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.GenerateTwoRowImageViewModel +import com.foke.together.util.AppLog +import kotlinx.coroutines.launch + +@Composable +fun GenerateTwoRowImageScreen( + navigateToShare: () -> Unit, + popBackStack: () -> Unit, + viewModel: GenerateTwoRowImageViewModel = 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.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) { + AppLog.d("GenerateTwoRowImageScreen", "LifecycleEventEffect: ON_RESUME", "${viewModel.imageUri}") + coroutineScope.launch { + viewModel.generateImage(graphicsLayer) + } + 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 { + GenerateTwoRowImageScreen( + 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/screen/ShareScreen.kt b/presenter/src/main/java/com/foke/together/presenter/screen/ShareScreen.kt index 73df5e4..87c8882 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,39 @@ fun ShareScreen( popBackStack: () -> Unit, viewModel: ShareViewModel = hiltViewModel() ) { + val finalSingleImageUri = viewModel.getFinalSingleImageUri() + val context = LocalContext.current 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 +87,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 +105,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 +123,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 +140,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 debb8e2..dd7aa07 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,9 +4,12 @@ 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 +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 @@ -20,7 +23,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,20 +35,21 @@ class CameraViewModel @Inject constructor( val captureCount: Int by _captureCount private var captureTimer: CountDownTimer? = null private var mTimerState = false - - // TODO: CameraScreen에서 filepath를 TimeUtil.getCurrentTimeSec() 로 전달 - // filepath를 ScameraScreen -> GenerateScreen -> ShareScreen 로 전달 - // 추후에는 filepath를 presenter에서 관리하지 않도록 해야 할듯 - val filepath = TimeUtil.getCurrentTimeSec() - - 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(filepath) + val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() + generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, "capture_${_captureCount.value}") _progressState.floatValue = 1f if (_captureCount.intValue < AppPolicy.CAPTURE_COUNT) { _captureCount.intValue += 1 @@ -52,6 +57,7 @@ class CameraViewModel @Inject constructor( } else { stopCaptureTimer() _captureCount.intValue = 1 + delay(CAPTURE_INTERVAL) nextNavigate() } } @@ -60,8 +66,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/GenerateSingleRowImageViewModel.kt b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt new file mode 100644 index 0000000..b55a793 --- /dev/null +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.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 GenerateSingleRowImageViewModel @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_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/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/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..75c0c9f 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 @@ -1,11 +1,33 @@ package com.foke.together.presenter.viewmodel +import android.content.Context +import android.net.Uri +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @HiltViewModel class ShareViewModel @Inject constructor( + @ApplicationContext private val context: Context ): ViewModel() { - // TODO: add viewmodel code here + + fun getFinalSingleImageUri(): Uri { + var finalSingleImageUri: Uri = Uri.EMPTY + context.cacheDir.listFiles().forEach { + file -> if (file.name.contains("final_single_row.jpg")) { finalSingleImageUri = Uri.fromFile(file) } + } + return finalSingleImageUri + } + fun shareImage() { + + } + fun printImage() { + + } + + fun downloadImage() { + + } } \ No newline at end of file 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 770dad0..0dae5a1 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,38 +17,43 @@ import kotlin.coroutines.resume object ImageFileUtil { - suspend fun saveGraphicsLayer( + suspend fun cacheBitmap( context: Context, - graphicsLayer: GraphicsLayer, - filepath: String, + bitmap: Bitmap, fileName: String ): Uri { - var uri : Uri = Uri.EMPTY - val bitmap = graphicsLayer.toImageBitmap() - bitmap.asAndroidBitmap().saveToInternalStorage(context, filepath, fileName) + val uri = bitmap.cache(context, fileName) return uri } - suspend fun saveBitmap( + suspend fun saveBitmapToStorage( context: Context, bitmap: Bitmap, - filepath: String, fileName: String ): Uri { - // TODO: implement to use uri - val uri : Uri = Uri.EMPTY - bitmap.saveToInternalStorage(context, filepath, fileName) + val uri = bitmap.saveToStorage(context, fileName) return uri } - private suspend fun Bitmap.saveToInternalStorage(context: Context, filepath: String, filename: String): Uri { - val baseDir = context.filesDir.absoluteFile - val path = File("$baseDir$filepath") - if (!path.exists()) { path.mkdirs() } + private suspend fun Bitmap.cache(context: Context, fileName: String): Uri { + val file = File( + context.cacheDir, + fileName + ".jpg" + ) + file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100) + + return Uri.fromFile(file) ?: Uri.EMPTY + } + + private suspend fun Bitmap.saveToStorage(context: Context, fileName: String): Uri { + val file = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), + fileName + ".jpg" + ) - val file = File(filepath, "$filename.jpeg") file.writeBitmap(this, Bitmap.CompressFormat.JPEG, 100) - return scanFilePath(context, file.path) ?: throw Exception("File could not be saved") + + return scanFilePath(context, file.absolutePath) ?: throw Exception("File could not be saved") } private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {