Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.foke.together.domain
package com.foke.together.domain.interactor

import com.foke.together.domain.output.AppPreferenceInterface
import kotlinx.coroutines.flow.Flow
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.foke.together.domain.interactor

import android.net.Uri
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class GetCapturedImageUriUseCase @Inject constructor(
private val internalCameraRepository: InternalCameraRepositoryInterface
) {
operator fun invoke() : Flow<Uri?> = internalCameraRepository.getCapturedImageUri()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.foke.together.domain
package com.foke.together.domain.interactor

import com.foke.together.domain.output.AppPreferenceInterface
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package com.foke.together.domain.output

import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import androidx.annotation.IntRange
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.view.PreviewView
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.flow.Flow

interface InternalCameraRepositoryInterface {

fun getCapturedImageUri() : Flow<Uri?>
suspend fun capture(
context: Context,
fileName : String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ImageRepository @Inject constructor(
MediaStore.Images.Media.DATE_ADDED
)
val selection = "${MediaStore.Images.Media.RELATIVE_PATH} LIKE ?"
val selectionArgs = arrayOf("%Pictures/4cuts/backup%")
val selectionArgs = arrayOf("%${AppPolicy.MEDIA_STORE_RELATIVE_PATH}%")
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"

val cursor = context.contentResolver.query(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.foke.together.external.repository
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.IntRange
Expand All @@ -20,6 +21,9 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.foke.together.domain.output.InternalCameraRepositoryInterface
import com.foke.together.util.AppLog
import com.foke.together.util.AppPolicy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -29,7 +33,9 @@ class InternalCameraRepository @Inject constructor(
private lateinit var cameraController: LifecycleCameraController
private lateinit var imageCapture: ImageCapture

private var imageBitmap : Bitmap? = null
private val capturedImageUri = MutableStateFlow<Uri?>(null)
override fun getCapturedImageUri(): Flow<Uri?> = capturedImageUri

override suspend fun capture(
context: Context,
fileName : String,
Expand All @@ -38,7 +44,7 @@ class InternalCameraRepository @Inject constructor(
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "${fileName}.jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/4cuts/backup")
put(MediaStore.Images.Media.RELATIVE_PATH, AppPolicy.MEDIA_STORE_RELATIVE_PATH)
}
val outputOptions = ImageCapture.OutputFileOptions.Builder(
context.contentResolver,
Expand All @@ -50,10 +56,8 @@ class InternalCameraRepository @Inject constructor(
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
if (outputFileResults.savedUri != null) {
imageBitmap = MediaStore.Images.Media.getBitmap(
context.contentResolver,
outputFileResults.savedUri
)
AppLog.d(TAG, "onImageSaved", "Photo capture succeeded: ${outputFileResults.savedUri}")
capturedImageUri.value = outputFileResults.savedUri
}
}
override fun onError(exception: ImageCaptureException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
Expand Down Expand Up @@ -462,4 +464,31 @@ fun AppBottomBar(
content = content
)
}
}

@Composable
fun AppCheckBox(
modifier: Modifier = Modifier,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
){
Checkbox(
modifier = modifier,
checked = checked,
onCheckedChange = onCheckedChange,
colors = CheckboxColors(
checkedCheckmarkColor = AppTheme.colorScheme.tint,
uncheckedCheckmarkColor = AppTheme.colorScheme.bottom,
checkedBoxColor = AppTheme.colorScheme.top,
uncheckedBoxColor = AppTheme.colorScheme.bottom,
disabledCheckedBoxColor = AppTheme.colorScheme.bottom,
disabledUncheckedBoxColor = AppTheme.colorScheme.bottom,
disabledIndeterminateBoxColor= AppTheme.colorScheme.bottom,
checkedBorderColor = AppTheme.colorScheme.border,
uncheckedBorderColor = AppTheme.colorScheme.top,
disabledBorderColor = AppTheme.colorScheme.border,
disabledUncheckedBorderColor = AppTheme.colorScheme.border,
disabledIndeterminateBorderColor= AppTheme.colorScheme.border,
)
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.foke.together.presenter.screen

import android.content.Context
import android.net.Uri
import androidx.camera.view.PreviewView
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
Expand All @@ -22,6 +23,7 @@ import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -44,6 +46,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.foke.together.presenter.R
import com.foke.together.presenter.component.AppBottomBar
import com.foke.together.presenter.component.AppTopBar
Expand All @@ -63,13 +67,14 @@ fun InternalCameraScreenRoot(
popBackStack: () -> Unit,
viewModel: InternelCameraViewModel = hiltViewModel()
){
val state = viewModel.state
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val captureCount by viewModel.captureCount.collectAsStateWithLifecycle()
val progress by viewModel.progressState.collectAsStateWithLifecycle()

val isFlashAnimationVisible by viewModel.flashAnimationState.collectAsStateWithLifecycle()
val countdownSeconds by viewModel.countdownSeconds.collectAsStateWithLifecycle()
val capturedImageUri by viewModel.capturedImageUri.collectAsState()
LifecycleEventEffect(Lifecycle.Event.ON_START) {
viewModel.setCaptureTimer(context) { navigateToGenerateImage() }
AppLog.d(TAG, "LifecycleEventEffect. ON_START", "")
Expand All @@ -83,11 +88,10 @@ fun InternalCameraScreenRoot(
AppLog.d(TAG, "LifecycleEventEffect. ON_STOP", "")
}
InternalCameraScreen(
state = state,
captureCount = captureCount,
progress = progress,
isFlashAnimationVisible = isFlashAnimationVisible,
countdownSeconds = countdownSeconds,
isFlashAnimationVisible = isFlashAnimationVisible,
capturedImageUri = capturedImageUri,
initialPreview = { context, previewView ->
viewModel.initial(context, lifecycleOwner, previewView)
},
Expand All @@ -100,11 +104,10 @@ fun InternalCameraScreenRoot(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InternalCameraScreen(
state : InternalCameraState,
captureCount : Int,
progress: Float,
isFlashAnimationVisible: Boolean,
countdownSeconds: Int,
capturedImageUri : Uri?,
initialPreview : (Context, PreviewView) -> Unit,
releasePreview : (Context) -> Unit,
){
Expand Down Expand Up @@ -185,7 +188,15 @@ fun InternalCameraScreen(
.padding(24.dp)
)
}

[email protected](
visible = capturedImageUri != null && countdownSeconds == 0,
) {
AsyncImage(
model = capturedImageUri,
contentDescription = "Captured Image",
modifier = Modifier.fillMaxSize()
)
}
}
}
}
Expand All @@ -197,18 +208,16 @@ fun InternalCameraScreen(
)
@Composable
fun InternalScreenPreview(){
val state by remember { mutableStateOf(InternalCameraState()) }
val captureCount by remember{ mutableStateOf(1) }
val progress by remember{ mutableStateOf(1f)}
val isFlashAnimationVisible by remember{ mutableStateOf(false)}
val countdownSeconds by remember{ mutableStateOf(3)}
val capturedImageUri by remember { mutableStateOf(Uri.EMPTY) }
FourCutTogetherTheme() {
InternalCameraScreen(
state = state,
captureCount = captureCount,
progress = progress,
isFlashAnimationVisible = isFlashAnimationVisible,
countdownSeconds = countdownSeconds,
capturedImageUri = capturedImageUri,
initialPreview = { context, previewView ->

},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.foke.together.presenter.screen

import android.content.res.Configuration
import android.net.Uri
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
Expand All @@ -15,6 +17,7 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -27,6 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
Expand All @@ -35,23 +39,26 @@ import com.foke.together.domain.interactor.entity.DefaultCutFrameSet
import com.foke.together.presenter.component.AppBottomBar
import com.foke.together.presenter.component.AppTopBar
import com.foke.together.presenter.component.BasicScaffold
import com.foke.together.presenter.frame.DefaultCutFrame
import com.foke.together.presenter.theme.AppTheme
import com.foke.together.presenter.theme.FourCutTogetherTheme
import com.foke.together.presenter.viewmodel.SelectFrameViewModel
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.foke.together.presenter.component.AppCheckBox

@Composable
fun SelectFrameScreen(
navigateToMethod: () -> Unit,
popBackStack: () -> Unit,
viewModel: SelectFrameViewModel = hiltViewModel()
) {
val cutFrames by viewModel.cutFrames.collectAsState()
val isDateDisplay by viewModel.isDateDisplay.collectAsState()
val isQRDisplay by viewModel.isQRDisplay.collectAsState()
val cutFrames by viewModel.cutFrames.collectAsStateWithLifecycle()
val isDateDisplay by viewModel.isDateDisplay.collectAsStateWithLifecycle()
val isQRDisplay by viewModel.isQRDisplay.collectAsStateWithLifecycle()

DisposableEffect(Unit) {
viewModel.updateSessionStatus()

onDispose { }
}
SelectFrameContent(
Expand All @@ -77,9 +84,9 @@ fun SelectFrameContent(
cutFrames: List<DefaultCutFrameSet>,
isDateDisplay: Boolean,
isQRDisplay : Boolean,
selectFrame: (cutFrame: CutFrame) -> Unit,
onDisplayDate : (Boolean ) -> Unit,
onDisplayQR : (Boolean) -> Unit,
selectFrame: ( cutFrame: CutFrame ) -> Unit,
onDisplayDate : ( Boolean ) -> Unit,
onDisplayQR : ( Boolean ) -> Unit,
){
BasicScaffold(
modifier = Modifier.fillMaxSize(),
Expand All @@ -95,11 +102,11 @@ fun SelectFrameContent(
modifier = Modifier.wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally
){
Checkbox(
AppCheckBox(
checked = isDateDisplay,
onCheckedChange = { isChecked ->
onDisplayDate(isChecked)
}
},
)
Text(
text = "날짜 표시하기",
Expand All @@ -111,7 +118,7 @@ fun SelectFrameContent(
modifier = Modifier.wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally
){
Checkbox(
AppCheckBox(
checked = isQRDisplay,
onCheckedChange = { isChecked ->
onDisplayQR(isChecked)
Expand Down Expand Up @@ -140,17 +147,29 @@ fun SelectFrameContent(
columns = GridCells.Fixed(6)
){
items(cutFrames.size){ index ->
Image(
modifier = Modifier.aspectRatio(0.3333f)
.clickable(
true,
onClick = {
selectFrame(cutFrames[index])
}
Box(
modifier = Modifier.fillMaxSize()
.clickable{
selectFrame(cutFrames[index])
},
contentAlignment = Alignment.Center,
){
DefaultCutFrame(
cutFrame = cutFrames[index],
imageUrlList = listOf(
"file:///android_asset/sample_cut.png".toUri(),
"file:///android_asset/sample_cut.png".toUri(),
"file:///android_asset/sample_cut.png".toUri(),
"file:///android_asset/sample_cut.png".toUri(),
),
painter = painterResource(id = cutFrames[index].frameImageSrc),
contentDescription = cutFrames[index].frameTitle
)
)
Text(
modifier = Modifier.align(Alignment.TopCenter),
text = cutFrames[index].frameTitle,
style = AppTheme.typography.body.copy(fontWeight = FontWeight.Bold),
color = AppTheme.colorScheme.top
)
}
}
}
}
Expand Down
Loading