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 a36f94d..93cec98 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 @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.net.Uri import com.foke.together.domain.interactor.entity.CutFrameType import com.foke.together.domain.output.ImageRepositoryInterface +import com.foke.together.util.AppPolicy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -20,4 +21,20 @@ class GeneratePhotoFrameUseCase @Inject constructor( 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) + + fun getFinalSingleImageUri(): Uri { + var finalSingleImageUri: Uri = Uri.EMPTY + context.cacheDir.listFiles().forEach { + file -> if (file.name.contains("${AppPolicy.SINGLE_ROW_FINAL_IMAGE_NAME}")) { finalSingleImageUri = Uri.fromFile(file) } + } + return finalSingleImageUri + } + + fun getFinalTwoImageUri(): Uri { + var finalTwoImageUri: Uri = Uri.EMPTY + context.cacheDir.listFiles().forEach { + file -> if (file.name.contains("${AppPolicy.TWO_ROW_FINAL_IMAGE_NAME}")) { finalTwoImageUri = Uri.fromFile(file) } + } + return finalTwoImageUri + } } \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/entity/FramePosition.kt b/domain/src/main/java/com/foke/together/domain/interactor/entity/FramePosition.kt new file mode 100644 index 0000000..d012d4e --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/entity/FramePosition.kt @@ -0,0 +1,13 @@ +package com.foke.together.domain.interactor.entity + +enum class FramePosition { + LEFT, + RIGHT; + fun findBy(ordinal: Int): FramePosition { + return when (ordinal) { + LEFT.ordinal -> LEFT + RIGHT.ordinal -> RIGHT + else -> throw IllegalArgumentException("Unknown value: $ordinal") + } + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/web/GetDownloadUrlUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/web/GetDownloadUrlUseCase.kt new file mode 100644 index 0000000..52d73a6 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/web/GetDownloadUrlUseCase.kt @@ -0,0 +1,18 @@ +package com.foke.together.domain.interactor.web + +import com.foke.together.util.AppPolicy +import javax.inject.Inject + +// Download url +// https://4cuts.store/download/{user_name}/{key} +class GetDownloadUrlUseCase @Inject constructor( + private val getCurrentUserInformationUseCase: GetCurrentUserInformationUseCase +) { + suspend operator fun invoke(key: String): Result { + getCurrentUserInformationUseCase() + .onSuccess { + "${AppPolicy.WEB_SERVER_URL}download/${it.name}/$key" + } + return Result.failure(Exception("Unknown error")) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/web/SessionKeyUseCase.kt b/domain/src/main/java/com/foke/together/domain/interactor/web/SessionKeyUseCase.kt new file mode 100644 index 0000000..7683d1d --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/web/SessionKeyUseCase.kt @@ -0,0 +1,16 @@ +package com.foke.together.domain.interactor.web + +import com.foke.together.domain.output.SessionRepositoryInterface +import javax.inject.Inject + +class SessionKeyUseCase @Inject constructor( + private val sessionRepository: SessionRepositoryInterface +) { + suspend fun setSessionKey() { + sessionRepository.setSessionKey() + } + + fun getSessionKey(): String { + return sessionRepository.getSessionKey() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/output/SessionRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/SessionRepositoryInterface.kt new file mode 100644 index 0000000..ce27d42 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/output/SessionRepositoryInterface.kt @@ -0,0 +1,6 @@ +package com.foke.together.domain.output + +interface SessionRepositoryInterface { + suspend fun setSessionKey() + fun getSessionKey(): String +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/repository/SessionRepository.kt b/external/src/main/java/com/foke/together/external/repository/SessionRepository.kt new file mode 100644 index 0000000..f6a6540 --- /dev/null +++ b/external/src/main/java/com/foke/together/external/repository/SessionRepository.kt @@ -0,0 +1,16 @@ +package com.foke.together.external.repository + +import com.foke.together.domain.output.SessionRepositoryInterface +import com.foke.together.util.TimeUtil +import javax.inject.Inject + +class SessionRepository @Inject constructor(): SessionRepositoryInterface { + private var sessionKey: String = "" + override suspend fun setSessionKey() { + sessionKey = TimeUtil.getCurrentTimeSec() + } + + override fun getSessionKey(): String { + return sessionKey + } +} \ 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 2d0e61b..c408c65 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 @@ -3,9 +3,11 @@ 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.domain.output.QRCodeRepositoryInterface +import com.foke.together.domain.output.SessionRepositoryInterface import com.foke.together.external.repository.ExternalCameraRepository import com.foke.together.external.repository.ImageRepository import com.foke.together.external.repository.QRCodeRepository +import com.foke.together.external.repository.SessionRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -32,4 +34,10 @@ abstract class RepositoryModule { abstract fun bindQRCodeRepository( qrCodeRepository: QRCodeRepository ): QRCodeRepositoryInterface + + @Singleton + @Binds + abstract fun bindSessionRepository( + sessionRepository: SessionRepository + ): SessionRepositoryInterface } \ No newline at end of file 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 0f95372..4b468c7 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 @@ -8,6 +8,7 @@ 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.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -26,6 +27,8 @@ 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.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -35,6 +38,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import coil.compose.AsyncImage import coil.request.ImageRequest +import com.foke.together.domain.interactor.entity.FramePosition import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.presenter.theme.highContrastDarkColorScheme @@ -48,13 +52,31 @@ import kotlinx.coroutines.launch fun FourCutFrame( // TODO: Need to refactoring. separate frame design with application theme designColorScheme: ColorScheme = mediumContrastLightColorScheme, - cameraImageUrlList : List? = null + cameraImageUrlList : List? = null, + position: FramePosition? = null ) { ConstraintLayout( modifier = Modifier .aspectRatio(ratio = 0.3333f) .background(color = designColorScheme.surface) .border(1.dp, designColorScheme.inverseSurface) + .padding( + start = position.let { + when(it){ + FramePosition.LEFT -> 20.dp + FramePosition.RIGHT -> 0.dp + null -> 10.dp + } + }, + end = position.let { + when(it){ + FramePosition.LEFT -> 0.dp + FramePosition.RIGHT -> 20.dp + null -> 10.dp + } + }, + top = 40.dp + ) ) { val (cameraColumn, decorateRow) = createRefs() LazyColumn( @@ -67,9 +89,8 @@ fun FourCutFrame( bottom.linkTo(decorateRow.top) start.linkTo(parent.start) end.linkTo(parent.end) - width = Dimension.fillToConstraints - height = Dimension.wrapContent } + .wrapContentSize() ) { items(AppPolicy.CAPTURE_COUNT){ //TODO: add camera image @@ -114,7 +135,9 @@ fun FourCutFrame( text = TimeUtil.getCurrentDisplayTime(), modifier = Modifier.weight(1f), color = designColorScheme.inverseSurface, - fontSize = 12.sp + fontSize = 15.sp, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold ) } } 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 5c01e4a..a3f41c7 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 @@ -6,6 +6,7 @@ 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 @@ -15,6 +16,7 @@ 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color @@ -24,6 +26,8 @@ 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.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -33,6 +37,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import coil.compose.AsyncImage import coil.request.ImageRequest +import com.foke.together.domain.interactor.entity.FramePosition import com.foke.together.presenter.R import com.foke.together.presenter.theme.FourCutTogetherTheme import com.foke.together.util.AppPolicy @@ -44,25 +49,40 @@ import kotlinx.coroutines.launch fun MakerFaireFrame( cameraImageUrlList : List? = null, backgroundColor : Color = Color(0xFFF5B934), - decorateImageUrl: String? = null + position: FramePosition? = null ) { ConstraintLayout( modifier = Modifier .aspectRatio(ratio = 0.3333f) .background(backgroundColor) .border(1.dp, Color.White) + .padding( + start = position.let { + when(it){ + FramePosition.LEFT -> 20.dp + FramePosition.RIGHT -> 0.dp + null -> 10.dp + } + }, + end = position.let { + when(it){ + FramePosition.LEFT -> 0.dp + FramePosition.RIGHT -> 20.dp + null -> 10.dp + } + }, + top = 40.dp + ) ) { - val (cameraColumn,decorateImage, curTime) = createRefs() - val logoImageGuideLineStart = createGuidelineFromStart(0.15f) - val logoImageGuideLineEnd = createGuidelineFromEnd(0.15f) + 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) } @@ -84,33 +104,34 @@ fun MakerFaireFrame( } } } - - Image( - painter = painterResource( - id = R.drawable.maker_faire_logo - ), - contentDescription = "for decorate", - modifier = Modifier.constrainAs(decorateImage){ - top.linkTo(cameraColumn.bottom) - bottom.linkTo(curTime.top) - start.linkTo(logoImageGuideLineStart) - end.linkTo(logoImageGuideLineEnd) - width = Dimension.fillToConstraints - height = Dimension.wrapContent - } - ) - - Text( - text = TimeUtil.getCurrentDisplayTime(), - modifier = Modifier.constrainAs(curTime){ - top.linkTo(decorateImage.bottom) - bottom.linkTo(parent.bottom, margin = 15.dp) - start.linkTo(parent.start) - end.linkTo(parent.end) - }, - color = Color.White, - fontSize = 12.sp - ) + Column( + modifier = Modifier + .constrainAs(decorateRow){ + top.linkTo(cameraColumn.bottom) + bottom.linkTo(parent.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + height = Dimension.fillToConstraints + width = Dimension.fillToConstraints + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource( + id = R.drawable.maker_faire_logo + ), + contentDescription = "for decorate", + modifier = Modifier.weight(1f) + ) + Text( + modifier = Modifier.weight(1f), + text = TimeUtil.getCurrentDisplayTime(), + color = Color.White, + fontSize = 15.sp, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold + ) + } } } 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 8afa406..9abcd4c 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 @@ -61,6 +61,7 @@ fun CameraScreen( end.linkTo(parent.end, margin = 24.dp) bottom.linkTo(title.top) width = Dimension.fillToConstraints + height = Dimension.wrapContent }, ) Text( 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 index 53098ba..6c6b48e 100644 --- a/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt +++ b/presenter/src/main/java/com/foke/together/presenter/screen/GenerateTwoRowImageScreen.kt @@ -20,6 +20,7 @@ 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.domain.interactor.entity.FramePosition import com.foke.together.presenter.frame.FourCutFrame import com.foke.together.presenter.frame.MakerFaireFrame import com.foke.together.presenter.theme.FourCutTogetherTheme @@ -70,11 +71,13 @@ fun GenerateTwoRowImageScreen( ) { GetFrame( cutFrameType = viewModel.cutFrameType.ordinal, - imageUri = viewModel.imageUri + imageUri = viewModel.imageUri, + position = FramePosition.LEFT ) GetFrame( cutFrameType = viewModel.cutFrameType.ordinal, - imageUri = viewModel.imageUri + imageUri = viewModel.imageUri, + position = FramePosition.RIGHT ) } } @@ -92,21 +95,25 @@ fun GenerateTwoRowImageScreen( @Composable fun GetFrame( cutFrameType : Int, - imageUri: List + imageUri: List, + position: FramePosition? = null ): Unit{ when(cutFrameType) { CutFrameType.MAKER_FAIRE.ordinal -> MakerFaireFrame( - cameraImageUrlList = imageUri + cameraImageUrlList = imageUri, + position = position ) CutFrameType.FOURCUT_LIGHT.ordinal -> FourCutFrame( designColorScheme = mediumContrastLightColorScheme, - cameraImageUrlList = imageUri + cameraImageUrlList = imageUri, + position = position ) CutFrameType.FOURCUT_DARK.ordinal -> FourCutFrame( designColorScheme = mediumContrastLightColorScheme, - cameraImageUrlList = imageUri + cameraImageUrlList = imageUri, + position = position ) else -> TODO() } 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 ba1c934..5403b2f 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 @@ -39,7 +39,9 @@ fun SelectFrameScreen( viewModel: SelectFrameViewModel = hiltViewModel() ) { FourCutTogetherTheme { - val pagerState = rememberPagerState { + val pagerState = rememberPagerState( + initialPage = CutFrameType.MAKER_FAIRE.ordinal + ) { CutFrameType.entries.size // 총 페이지 수 설정 } ConstraintLayout( @@ -102,15 +104,17 @@ fun SelectFrameScreen( ) ) { page -> when(page){ + CutFrameType.MAKER_FAIRE.ordinal -> 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( - onClick = { navigateToMethod() }, + onClick = { + viewModel.setCutFrameType(pagerState.currentPage) + navigateToMethod() + }, modifier = Modifier.constrainAs(frameSelectButton) { top.linkTo(pager.bottom) start.linkTo(startGuideLine) 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 fb72eb0..10bf65b 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,6 +1,11 @@ package com.foke.together.presenter.screen +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size @@ -10,8 +15,14 @@ import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Print import androidx.compose.material.icons.filled.Share -import androidx.compose.material3.* +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview @@ -29,6 +40,7 @@ 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.viewmodel.ShareViewModel import com.foke.together.util.ImageFileUtil @@ -37,131 +49,121 @@ fun ShareScreen( popBackStack: () -> Unit, viewModel: ShareViewModel = hiltViewModel() ) { - val finalSingleImageUri = viewModel.getFinalSingleImageUri() val context = LocalContext.current + val qrCodeBitmap: State = viewModel.qrCodeBitmap.collectAsState(initial = Bitmap.createBitmap( + 1,1,Bitmap.Config.ARGB_8888) ) ConstraintLayout( modifier = Modifier.fillMaxSize() ) { - val (finalPic, printButton, shareButton, downloadButton, homeButton ) = createRefs() + val (finalPic, buttonColumn, 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 - ) + val startGuideLine = createGuidelineFromStart(0.1f) + val endGuideLine = createGuidelineFromEnd(0.1f) // TODO: need check to change single ImageView AsyncImage( model = ImageRequest.Builder(context) - .data(finalSingleImageUri) + .data(viewModel.singleImageUri) .build(), contentDescription = "", modifier = Modifier .constrainAs(finalPic) { top.linkTo(topGuideLine) bottom.linkTo(bottomGuideLine) - start.linkTo(parent.start, margin = 30.dp) + start.linkTo(startGuideLine) + end.linkTo(buttonColumn.start) width = Dimension.wrapContent height = Dimension.fillToConstraints } .aspectRatio(0.3333f) ) - IconButton( - onClick = { popBackStack() }, - modifier = Modifier.constrainAs(homeButton) { + Column( + modifier = Modifier.constrainAs(buttonColumn){ top.linkTo(topGuideLine) - end.linkTo(parent.end, margin = 30.dp) - bottom.linkTo(printButton.top) - height = Dimension.wrapContent - width = Dimension.wrapContent + bottom.linkTo(bottomGuideLine) + start.linkTo(finalPic.end) + end.linkTo(endGuideLine) + width = Dimension.fillToConstraints + height = Dimension.fillToConstraints }, + horizontalAlignment = Alignment.CenterHorizontally ) { - Icon( - modifier = Modifier.size(120.dp), - imageVector = Icons.Filled.Home, - contentDescription = "Home", - tint = MaterialTheme.colorScheme.primary - ) - } - IconButton( - onClick = { - val finalTwoRowImageUri = viewModel.getFinalTwoImageUri() - ImageFileUtil.printFromUri(context,finalTwoRowImageUri) - }, - modifier = Modifier.constrainAs(printButton) { - top.linkTo(homeButton.bottom) - end.linkTo(parent.end, margin = 30.dp) - bottom.linkTo(shareButton.top) - height = Dimension.wrapContent - width = Dimension.wrapContent - }, - ) { - Icon( - modifier = Modifier.size(120.dp), - imageVector = Icons.Filled.Print, - contentDescription = "Print", - tint = MaterialTheme.colorScheme.primary - ) - } + IconButton( + onClick = { popBackStack() }, + modifier = Modifier.weight(1f) + ) { + Icon( + modifier = Modifier.fillMaxSize(), + imageVector = Icons.Filled.Home, + contentDescription = "Home", + tint = MaterialTheme.colorScheme.primary + ) + } - IconButton( - onClick = { - val finalFile = finalSingleImageUri.toFile() - val contentUri = FileProvider.getUriForFile( - context, - "com.foke.together.fileprovider", - finalFile + IconButton( + onClick = { + ImageFileUtil.printFromUri(context,viewModel.twoImageUri) + }, + modifier = Modifier.weight(1f) + ) { + Icon( + modifier = Modifier.fillMaxSize(), + imageVector = Icons.Filled.Print, + contentDescription = "Print", + tint = MaterialTheme.colorScheme.primary ) - ImageFileUtil.shareUri(context, contentUri) - }, - modifier = Modifier.constrainAs(shareButton) { - top.linkTo(printButton.bottom) - end.linkTo(parent.end, margin = 30.dp) - bottom.linkTo(downloadButton.top) - height = Dimension.wrapContent - width = Dimension.wrapContent - }, - ) { - Icon( - modifier = Modifier.size(120.dp), - imageVector = Icons.Filled.Share, - contentDescription = "Share", - tint = MaterialTheme.colorScheme.primary - ) - } + } - IconButton( - onClick = { viewModel.downloadImage() }, - modifier = Modifier.constrainAs(downloadButton) { - top.linkTo(shareButton.bottom) - end.linkTo(parent.end, margin = 30.dp) - bottom.linkTo(parent.bottom) - height = Dimension.wrapContent - width = Dimension.wrapContent - }, - ) { - Icon( - modifier = Modifier.size(120.dp), - imageVector = Icons.Filled.Download, - contentDescription = "Download", - tint = MaterialTheme.colorScheme.primary - ) + IconButton( + onClick = { + val contentUri = FileProvider.getUriForFile( + context, + "com.foke.together.fileprovider", + viewModel.singleImageUri.toFile() + ) + ImageFileUtil.shareUri(context, contentUri) + }, + modifier = Modifier.weight(1f) + ) { + Icon( + modifier = Modifier.fillMaxSize(), + imageVector = Icons.Filled.Share, + contentDescription = "Share", + tint = MaterialTheme.colorScheme.primary + ) + } + + Box( + modifier = Modifier.weight(1f) + ){ + AsyncImage( + model = ImageRequest.Builder(context) + .data(qrCodeBitmap.value) + .build(), + contentDescription = "qr code", + modifier = Modifier.fillMaxSize() + ) + } + +// IconButton( +// onClick = { +// viewModel.downloadImage() +// }, +// modifier = Modifier.weight(1f) +// ) { +// Icon( +// modifier = Modifier.fillMaxSize(), +// imageVector = Icons.Filled.Download, +// contentDescription = "Download", +// tint = MaterialTheme.colorScheme.primary +// ) +// } } } - // 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 57f72c2..898c2c4 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 @@ -1,5 +1,6 @@ package com.foke.together.presenter.viewmodel +import android.content.Context import android.os.CountDownTimer import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf @@ -11,20 +12,25 @@ 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.domain.interactor.web.SessionKeyUseCase import com.foke.together.util.AppPolicy import com.foke.together.util.AppPolicy.CAPTURE_INTERVAL import com.foke.together.util.AppPolicy.COUNTDOWN_INTERVAL +import com.foke.together.util.SoundUtil import com.foke.together.util.TimeUtil import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class CameraViewModel @Inject constructor( + @ApplicationContext private val context: Context, getExternalCameraPreviewUrlUseCase: GetExternalCameraPreviewUrlUseCase, // private val captureWithExternalCameraUseCase: CaptureWithExternalCameraUseCase, - private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase, + private val sessionKeyUseCase: SessionKeyUseCase ): ViewModel() { val externalCameraIP = getExternalCameraPreviewUrlUseCase() @@ -48,6 +54,7 @@ class CameraViewModel @Inject constructor( } override fun onFinish() { viewModelScope.launch { + SoundUtil.getCameraSound(context = context ) val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, "${AppPolicy.CAPTURED_FOUR_CUT_IMAGE_NAME}_${_captureCount.value}") _progressState.floatValue = 1f @@ -55,9 +62,9 @@ class CameraViewModel @Inject constructor( _captureCount.intValue += 1 mTimerState = false } else { + sessionKeyUseCase.setSessionKey() stopCaptureTimer() _captureCount.intValue = 1 - delay(CAPTURE_INTERVAL) nextNavigate() } } 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 index 682c72a..19a3e4a 100644 --- a/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt +++ b/presenter/src/main/java/com/foke/together/presenter/viewmodel/GenerateSingleRowImageViewModel.kt @@ -3,9 +3,13 @@ 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.core.net.toFile import androidx.lifecycle.ViewModel import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase +import com.foke.together.domain.interactor.GetQRCodeUseCase import com.foke.together.domain.interactor.entity.CutFrameType +import com.foke.together.domain.interactor.web.SessionKeyUseCase +import com.foke.together.domain.interactor.web.UploadFileUseCase import com.foke.together.util.AppLog import com.foke.together.util.AppPolicy import dagger.hilt.android.lifecycle.HiltViewModel @@ -15,7 +19,10 @@ import javax.inject.Inject @HiltViewModel class GenerateSingleRowImageViewModel @Inject constructor( @ApplicationContext private val context: Context, - private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase, + private val uploadFileUseCase: UploadFileUseCase, + private val sessionKeyUseCase: SessionKeyUseCase, + private val getQRCodeUseCase: GetQRCodeUseCase ): ViewModel() { val cutFrameType: CutFrameType = generatePhotoFrameUseCase.getCutFrameType() val imageUri = generatePhotoFrameUseCase.getCapturedImageListUri() @@ -23,6 +30,8 @@ class GenerateSingleRowImageViewModel @Inject constructor( suspend fun generateImage(graphicsLayer: GraphicsLayer) { val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap() val finalCachedImageUri = generatePhotoFrameUseCase.saveGraphicsLayerImage(bitmap, AppPolicy.SINGLE_ROW_FINAL_IMAGE_NAME) + val result = uploadFileUseCase(sessionKeyUseCase.getSessionKey(), finalCachedImageUri.toFile()) AppLog.d("GenerateImageViewModel", "generateTwoRowImage" ,"twoRow: $finalCachedImageUri") + AppLog.d("GenerateImageViewModel", "UploadFile" ,"result: $result") } } \ 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 979f1e9..e3ee0c8 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,40 +1,45 @@ package com.foke.together.presenter.viewmodel import android.content.Context +import android.graphics.Bitmap import android.net.Uri +import androidx.core.content.FileProvider +import androidx.core.net.toFile import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.foke.together.domain.interactor.GeneratePhotoFrameUseCase +import com.foke.together.domain.interactor.GetQRCodeUseCase +import com.foke.together.domain.interactor.web.GetDownloadUrlUseCase +import com.foke.together.domain.interactor.web.SessionKeyUseCase import com.foke.together.util.AppPolicy import com.foke.together.util.ImageFileUtil import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ActivityContext 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 ShareViewModel @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + private val getQRCodeUseCase: GetQRCodeUseCase, + private val getDownloadUrlUseCase: GetDownloadUrlUseCase, + private val sessionKeyUseCase: SessionKeyUseCase, + private val generatePhotoFrameUseCase: GeneratePhotoFrameUseCase ): ViewModel() { - - fun getFinalSingleImageUri(): Uri { - var finalSingleImageUri: Uri = Uri.EMPTY - context.cacheDir.listFiles().forEach { - file -> if (file.name.contains("${AppPolicy.SINGLE_ROW_FINAL_IMAGE_NAME}")) { finalSingleImageUri = Uri.fromFile(file) } - } - return finalSingleImageUri + val qrCodeBitmap: Flow = flow{ + val qrCodeBitmap = generateQRcode() + emit(qrCodeBitmap) } - fun getFinalTwoImageUri(): Uri { - var finalTwoImageUri: Uri = Uri.EMPTY - context.cacheDir.listFiles().forEach { - file -> if (file.name.contains("${AppPolicy.TWO_ROW_FINAL_IMAGE_NAME}")) { finalTwoImageUri = Uri.fromFile(file) } - } - return finalTwoImageUri - } + val singleImageUri: Uri = generatePhotoFrameUseCase.getFinalSingleImageUri() + + val twoImageUri: Uri = generatePhotoFrameUseCase.getFinalTwoImageUri() fun downloadImage() { - val imageUri = getFinalSingleImageUri() - val imageBitmap = ImageFileUtil.getBitmapFromUri(context, imageUri) + val imageBitmap = ImageFileUtil.getBitmapFromUri(context, singleImageUri) viewModelScope.launch { ImageFileUtil.saveBitmapToStorage( context, @@ -43,4 +48,32 @@ class ShareViewModel @Inject constructor( ) } } + + suspend fun generateQRcode(): Bitmap { + val sessionKey = sessionKeyUseCase.getSessionKey() + val downloadUrl:String = getDownloadUrlUseCase(sessionKey).toString() + if(downloadUrl.contains("Failure")){ + return Bitmap.createBitmap( + 1,1,Bitmap.Config.ARGB_8888) + } + return getQRCodeUseCase(sessionKey, downloadUrl) + // return a empty bitmap if the qr code generation fails + .getOrElse( + return Bitmap.createBitmap( + 1,1,Bitmap.Config.ARGB_8888) + ) + } + + fun shareImage() { + val contentUri = FileProvider.getUriForFile( + context, + "com.foke.together.fileprovider", + singleImageUri.toFile() + ) + ImageFileUtil.shareUri(context, contentUri) + } + + fun printImage(activityContext: Context) { + ImageFileUtil.printFromUri(activityContext,twoImageUri) + } } \ No newline at end of file diff --git a/presenter/src/main/res/drawable/fourcut_frame_high_dark.png b/presenter/src/main/res/drawable/fourcut_frame_high_dark.png deleted file mode 100644 index 4c1524d..0000000 Binary files a/presenter/src/main/res/drawable/fourcut_frame_high_dark.png and /dev/null differ diff --git a/presenter/src/main/res/drawable/fourcut_frame_high_light.png b/presenter/src/main/res/drawable/fourcut_frame_high_light.png deleted file mode 100644 index d38a616..0000000 Binary files a/presenter/src/main/res/drawable/fourcut_frame_high_light.png and /dev/null differ diff --git a/presenter/src/main/res/drawable/fourcut_frame_medium_dark.png b/presenter/src/main/res/drawable/fourcut_frame_medium_dark.png index 17f8da8..ae24eb2 100644 Binary files a/presenter/src/main/res/drawable/fourcut_frame_medium_dark.png and b/presenter/src/main/res/drawable/fourcut_frame_medium_dark.png differ diff --git a/presenter/src/main/res/drawable/fourcut_frame_medium_light.png b/presenter/src/main/res/drawable/fourcut_frame_medium_light.png index b66ca5a..1b6217b 100644 Binary files a/presenter/src/main/res/drawable/fourcut_frame_medium_light.png and b/presenter/src/main/res/drawable/fourcut_frame_medium_light.png differ diff --git a/presenter/src/main/res/drawable/maker_faire_frame.png b/presenter/src/main/res/drawable/maker_faire_frame.png index b0f555c..6256e4d 100644 Binary files a/presenter/src/main/res/drawable/maker_faire_frame.png and b/presenter/src/main/res/drawable/maker_faire_frame.png differ diff --git a/util/src/main/java/com/foke/together/util/SoundUtil.kt b/util/src/main/java/com/foke/together/util/SoundUtil.kt new file mode 100644 index 0000000..886aef7 --- /dev/null +++ b/util/src/main/java/com/foke/together/util/SoundUtil.kt @@ -0,0 +1,17 @@ +package com.foke.together.util + +import android.content.Context +import android.media.SoundPool + +object SoundUtil { + private val soundPool = SoundPool.Builder().build() + private var cameraSoundId = 0 + private val onLoadCompleteListener = SoundPool.OnLoadCompleteListener { soundPool, sampleId, status -> + soundPool.play(cameraSoundId, 1.0f, 1.0f, 0, 0, 1.0f) + } + + fun getCameraSound(context: Context) { + cameraSoundId = soundPool.load(context,R.raw.capture_sound, 1) + soundPool.setOnLoadCompleteListener(onLoadCompleteListener) + } +} \ No newline at end of file diff --git a/util/src/main/res/raw/capture_sound.mp3 b/util/src/main/res/raw/capture_sound.mp3 new file mode 100644 index 0000000..cf79b87 Binary files /dev/null and b/util/src/main/res/raw/capture_sound.mp3 differ