Skip to content

Commit a77a334

Browse files
committed
촬영 후 프리뷰 2초동안 보여주기 [#158]
- ShareScreen Home 버튼 추가 - CaptureDuration 설정 추가 - CheckBox 디자인 시스템 추가
1 parent 18b997e commit a77a334

File tree

16 files changed

+193
-79
lines changed

16 files changed

+193
-79
lines changed

domain/src/main/java/com/foke/together/domain/GetCaptureDurationUseCase.kt renamed to domain/src/main/java/com/foke/together/domain/interactor/GetCaptureDurationUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.foke.together.domain
1+
package com.foke.together.domain.interactor
22

33
import com.foke.together.domain.output.AppPreferenceInterface
44
import kotlinx.coroutines.flow.Flow
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.foke.together.domain.interactor
2+
3+
import android.net.Uri
4+
import com.foke.together.domain.output.InternalCameraRepositoryInterface
5+
import kotlinx.coroutines.flow.Flow
6+
import javax.inject.Inject
7+
8+
class GetCapturedImageUriUseCase @Inject constructor(
9+
private val internalCameraRepository: InternalCameraRepositoryInterface
10+
) {
11+
operator fun invoke() : Flow<Uri?> = internalCameraRepository.getCapturedImageUri()
12+
}

domain/src/main/java/com/foke/together/domain/SetCaptureDurationUseCase.kt renamed to domain/src/main/java/com/foke/together/domain/interactor/SetCaptureDurationUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.foke.together.domain
1+
package com.foke.together.domain.interactor
22

33
import com.foke.together.domain.output.AppPreferenceInterface
44
import javax.inject.Inject

domain/src/main/java/com/foke/together/domain/output/InternalCameraRepositoryInterface.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ package com.foke.together.domain.output
22

33
import android.content.Context
44
import android.graphics.Bitmap
5+
import android.net.Uri
56
import androidx.annotation.IntRange
67
import androidx.camera.core.CameraSelector
78
import androidx.camera.core.ImageAnalysis
89
import androidx.camera.view.PreviewView
910
import androidx.lifecycle.LifecycleOwner
11+
import kotlinx.coroutines.flow.Flow
1012

1113
interface InternalCameraRepositoryInterface {
14+
15+
fun getCapturedImageUri() : Flow<Uri?>
1216
suspend fun capture(
1317
context: Context,
1418
fileName : String,

external/src/main/java/com/foke/together/external/repository/ImageRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ImageRepository @Inject constructor(
4747
MediaStore.Images.Media.DATE_ADDED
4848
)
4949
val selection = "${MediaStore.Images.Media.RELATIVE_PATH} LIKE ?"
50-
val selectionArgs = arrayOf("%Pictures/4cuts/backup%")
50+
val selectionArgs = arrayOf("%${AppPolicy.MEDIA_STORE_RELATIVE_PATH}%")
5151
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"
5252

5353
val cursor = context.contentResolver.query(

external/src/main/java/com/foke/together/external/repository/InternalCameraRepository.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.foke.together.external.repository
33
import android.content.ContentValues
44
import android.content.Context
55
import android.graphics.Bitmap
6+
import android.net.Uri
67
import android.os.Build
78
import android.provider.MediaStore
89
import androidx.annotation.IntRange
@@ -20,6 +21,9 @@ import androidx.core.content.ContextCompat
2021
import androidx.lifecycle.LifecycleOwner
2122
import com.foke.together.domain.output.InternalCameraRepositoryInterface
2223
import com.foke.together.util.AppLog
24+
import com.foke.together.util.AppPolicy
25+
import kotlinx.coroutines.flow.Flow
26+
import kotlinx.coroutines.flow.MutableStateFlow
2327
import javax.inject.Inject
2428
import javax.inject.Singleton
2529

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

32-
private var imageBitmap : Bitmap? = null
36+
private val capturedImageUri = MutableStateFlow<Uri?>(null)
37+
override fun getCapturedImageUri(): Flow<Uri?> = capturedImageUri
38+
3339
override suspend fun capture(
3440
context: Context,
3541
fileName : String,
@@ -38,7 +44,7 @@ class InternalCameraRepository @Inject constructor(
3844
val contentValues = ContentValues().apply {
3945
put(MediaStore.MediaColumns.DISPLAY_NAME, "${fileName}.jpg")
4046
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
41-
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/4cuts/backup")
47+
put(MediaStore.Images.Media.RELATIVE_PATH, AppPolicy.MEDIA_STORE_RELATIVE_PATH)
4248
}
4349
val outputOptions = ImageCapture.OutputFileOptions.Builder(
4450
context.contentResolver,
@@ -50,10 +56,8 @@ class InternalCameraRepository @Inject constructor(
5056
object : ImageCapture.OnImageSavedCallback {
5157
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
5258
if (outputFileResults.savedUri != null) {
53-
imageBitmap = MediaStore.Images.Media.getBitmap(
54-
context.contentResolver,
55-
outputFileResults.savedUri
56-
)
59+
AppLog.d(TAG, "onImageSaved", "Photo capture succeeded: ${outputFileResults.savedUri}")
60+
capturedImageUri.value = outputFileResults.savedUri
5761
}
5862
}
5963
override fun onError(exception: ImageCaptureException) {

presenter/src/main/java/com/foke/together/presenter/component/Basic.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import androidx.compose.material3.ButtonColors
2121
import androidx.compose.material3.Card
2222
import androidx.compose.material3.CardDefaults
2323
import androidx.compose.material3.CenterAlignedTopAppBar
24+
import androidx.compose.material3.Checkbox
25+
import androidx.compose.material3.CheckboxColors
2426
import androidx.compose.material3.ExperimentalMaterial3Api
2527
import androidx.compose.material3.Icon
2628
import androidx.compose.material3.Scaffold
@@ -462,4 +464,31 @@ fun AppBottomBar(
462464
content = content
463465
)
464466
}
467+
}
468+
469+
@Composable
470+
fun AppCheckBox(
471+
modifier: Modifier = Modifier,
472+
checked: Boolean,
473+
onCheckedChange: (Boolean) -> Unit,
474+
){
475+
Checkbox(
476+
modifier = modifier,
477+
checked = checked,
478+
onCheckedChange = onCheckedChange,
479+
colors = CheckboxColors(
480+
checkedCheckmarkColor = AppTheme.colorScheme.tint,
481+
uncheckedCheckmarkColor = AppTheme.colorScheme.bottom,
482+
checkedBoxColor = AppTheme.colorScheme.top,
483+
uncheckedBoxColor = AppTheme.colorScheme.bottom,
484+
disabledCheckedBoxColor = AppTheme.colorScheme.bottom,
485+
disabledUncheckedBoxColor = AppTheme.colorScheme.bottom,
486+
disabledIndeterminateBoxColor= AppTheme.colorScheme.bottom,
487+
checkedBorderColor = AppTheme.colorScheme.border,
488+
uncheckedBorderColor = AppTheme.colorScheme.top,
489+
disabledBorderColor = AppTheme.colorScheme.border,
490+
disabledUncheckedBorderColor = AppTheme.colorScheme.border,
491+
disabledIndeterminateBorderColor= AppTheme.colorScheme.border,
492+
)
493+
)
465494
}

presenter/src/main/java/com/foke/together/presenter/screen/InternalCameraScreen.kt

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.foke.together.presenter.screen
22

33
import android.content.Context
4+
import android.net.Uri
45
import androidx.camera.view.PreviewView
56
import androidx.compose.animation.core.animateFloatAsState
67
import androidx.compose.animation.core.tween
@@ -22,6 +23,7 @@ import androidx.compose.material3.BottomAppBar
2223
import androidx.compose.material3.ExperimentalMaterial3Api
2324
import androidx.compose.material3.Text
2425
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.collectAsState
2527
import androidx.compose.runtime.getValue
2628
import androidx.compose.runtime.mutableStateOf
2729
import androidx.compose.runtime.remember
@@ -44,6 +46,8 @@ import androidx.lifecycle.Lifecycle
4446
import androidx.lifecycle.compose.LifecycleEventEffect
4547
import androidx.lifecycle.compose.LocalLifecycleOwner
4648
import androidx.lifecycle.compose.collectAsStateWithLifecycle
49+
import coil.compose.AsyncImage
50+
import coil.request.ImageRequest
4751
import com.foke.together.presenter.R
4852
import com.foke.together.presenter.component.AppBottomBar
4953
import com.foke.together.presenter.component.AppTopBar
@@ -63,13 +67,14 @@ fun InternalCameraScreenRoot(
6367
popBackStack: () -> Unit,
6468
viewModel: InternelCameraViewModel = hiltViewModel()
6569
){
66-
val state = viewModel.state
6770
val lifecycleOwner = LocalLifecycleOwner.current
6871
val context = LocalContext.current
6972
val captureCount by viewModel.captureCount.collectAsStateWithLifecycle()
7073
val progress by viewModel.progressState.collectAsStateWithLifecycle()
74+
7175
val isFlashAnimationVisible by viewModel.flashAnimationState.collectAsStateWithLifecycle()
7276
val countdownSeconds by viewModel.countdownSeconds.collectAsStateWithLifecycle()
77+
val capturedImageUri by viewModel.capturedImageUri.collectAsState()
7378
LifecycleEventEffect(Lifecycle.Event.ON_START) {
7479
viewModel.setCaptureTimer(context) { navigateToGenerateImage() }
7580
AppLog.d(TAG, "LifecycleEventEffect. ON_START", "")
@@ -83,11 +88,10 @@ fun InternalCameraScreenRoot(
8388
AppLog.d(TAG, "LifecycleEventEffect. ON_STOP", "")
8489
}
8590
InternalCameraScreen(
86-
state = state,
8791
captureCount = captureCount,
88-
progress = progress,
89-
isFlashAnimationVisible = isFlashAnimationVisible,
9092
countdownSeconds = countdownSeconds,
93+
isFlashAnimationVisible = isFlashAnimationVisible,
94+
capturedImageUri = capturedImageUri,
9195
initialPreview = { context, previewView ->
9296
viewModel.initial(context, lifecycleOwner, previewView)
9397
},
@@ -100,11 +104,10 @@ fun InternalCameraScreenRoot(
100104
@OptIn(ExperimentalMaterial3Api::class)
101105
@Composable
102106
fun InternalCameraScreen(
103-
state : InternalCameraState,
104107
captureCount : Int,
105-
progress: Float,
106108
isFlashAnimationVisible: Boolean,
107109
countdownSeconds: Int,
110+
capturedImageUri : Uri?,
108111
initialPreview : (Context, PreviewView) -> Unit,
109112
releasePreview : (Context) -> Unit,
110113
){
@@ -185,7 +188,15 @@ fun InternalCameraScreen(
185188
.padding(24.dp)
186189
)
187190
}
188-
191+
this@Column.AnimatedVisibility(
192+
visible = capturedImageUri != null && countdownSeconds == 0,
193+
) {
194+
AsyncImage(
195+
model = capturedImageUri,
196+
contentDescription = "Captured Image",
197+
modifier = Modifier.fillMaxSize()
198+
)
199+
}
189200
}
190201
}
191202
}
@@ -197,18 +208,16 @@ fun InternalCameraScreen(
197208
)
198209
@Composable
199210
fun InternalScreenPreview(){
200-
val state by remember { mutableStateOf(InternalCameraState()) }
201211
val captureCount by remember{ mutableStateOf(1) }
202-
val progress by remember{ mutableStateOf(1f)}
203212
val isFlashAnimationVisible by remember{ mutableStateOf(false)}
204213
val countdownSeconds by remember{ mutableStateOf(3)}
214+
val capturedImageUri by remember { mutableStateOf(Uri.EMPTY) }
205215
FourCutTogetherTheme() {
206216
InternalCameraScreen(
207-
state = state,
208217
captureCount = captureCount,
209-
progress = progress,
210218
isFlashAnimationVisible = isFlashAnimationVisible,
211219
countdownSeconds = countdownSeconds,
220+
capturedImageUri = capturedImageUri,
212221
initialPreview = { context, previewView ->
213222

214223
},

presenter/src/main/java/com/foke/together/presenter/screen/SelectFrameScreen.kt

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.foke.together.presenter.screen
22

33
import android.content.res.Configuration
4+
import android.net.Uri
45
import androidx.compose.foundation.Image
56
import androidx.compose.foundation.clickable
67
import androidx.compose.foundation.layout.Arrangement
8+
import androidx.compose.foundation.layout.Box
79
import androidx.compose.foundation.layout.Column
810
import androidx.compose.foundation.layout.Row
911
import androidx.compose.foundation.layout.aspectRatio
@@ -15,6 +17,7 @@ import androidx.compose.foundation.layout.wrapContentSize
1517
import androidx.compose.foundation.lazy.grid.GridCells
1618
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
1719
import androidx.compose.material3.Checkbox
20+
import androidx.compose.material3.CheckboxColors
1821
import androidx.compose.material3.ExperimentalMaterial3Api
1922
import androidx.compose.material3.Text
2023
import androidx.compose.runtime.Composable
@@ -27,6 +30,7 @@ import androidx.compose.runtime.setValue
2730
import androidx.compose.ui.Alignment
2831
import androidx.compose.ui.Modifier
2932
import androidx.compose.ui.res.painterResource
33+
import androidx.compose.ui.text.font.FontWeight
3034
import androidx.compose.ui.tooling.preview.Devices
3135
import androidx.compose.ui.tooling.preview.Preview
3236
import androidx.hilt.navigation.compose.hiltViewModel
@@ -35,23 +39,26 @@ import com.foke.together.domain.interactor.entity.DefaultCutFrameSet
3539
import com.foke.together.presenter.component.AppBottomBar
3640
import com.foke.together.presenter.component.AppTopBar
3741
import com.foke.together.presenter.component.BasicScaffold
42+
import com.foke.together.presenter.frame.DefaultCutFrame
3843
import com.foke.together.presenter.theme.AppTheme
3944
import com.foke.together.presenter.theme.FourCutTogetherTheme
4045
import com.foke.together.presenter.viewmodel.SelectFrameViewModel
46+
import androidx.core.net.toUri
47+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
48+
import com.foke.together.presenter.component.AppCheckBox
4149

4250
@Composable
4351
fun SelectFrameScreen(
4452
navigateToMethod: () -> Unit,
4553
popBackStack: () -> Unit,
4654
viewModel: SelectFrameViewModel = hiltViewModel()
4755
) {
48-
val cutFrames by viewModel.cutFrames.collectAsState()
49-
val isDateDisplay by viewModel.isDateDisplay.collectAsState()
50-
val isQRDisplay by viewModel.isQRDisplay.collectAsState()
56+
val cutFrames by viewModel.cutFrames.collectAsStateWithLifecycle()
57+
val isDateDisplay by viewModel.isDateDisplay.collectAsStateWithLifecycle()
58+
val isQRDisplay by viewModel.isQRDisplay.collectAsStateWithLifecycle()
5159

5260
DisposableEffect(Unit) {
5361
viewModel.updateSessionStatus()
54-
5562
onDispose { }
5663
}
5764
SelectFrameContent(
@@ -77,9 +84,9 @@ fun SelectFrameContent(
7784
cutFrames: List<DefaultCutFrameSet>,
7885
isDateDisplay: Boolean,
7986
isQRDisplay : Boolean,
80-
selectFrame: (cutFrame: CutFrame) -> Unit,
81-
onDisplayDate : (Boolean ) -> Unit,
82-
onDisplayQR : (Boolean) -> Unit,
87+
selectFrame: ( cutFrame: CutFrame ) -> Unit,
88+
onDisplayDate : ( Boolean ) -> Unit,
89+
onDisplayQR : ( Boolean ) -> Unit,
8390
){
8491
BasicScaffold(
8592
modifier = Modifier.fillMaxSize(),
@@ -95,11 +102,11 @@ fun SelectFrameContent(
95102
modifier = Modifier.wrapContentSize(),
96103
horizontalAlignment = Alignment.CenterHorizontally
97104
){
98-
Checkbox(
105+
AppCheckBox(
99106
checked = isDateDisplay,
100107
onCheckedChange = { isChecked ->
101108
onDisplayDate(isChecked)
102-
}
109+
},
103110
)
104111
Text(
105112
text = "날짜 표시하기",
@@ -111,7 +118,7 @@ fun SelectFrameContent(
111118
modifier = Modifier.wrapContentSize(),
112119
horizontalAlignment = Alignment.CenterHorizontally
113120
){
114-
Checkbox(
121+
AppCheckBox(
115122
checked = isQRDisplay,
116123
onCheckedChange = { isChecked ->
117124
onDisplayQR(isChecked)
@@ -140,17 +147,29 @@ fun SelectFrameContent(
140147
columns = GridCells.Fixed(6)
141148
){
142149
items(cutFrames.size){ index ->
143-
Image(
144-
modifier = Modifier.aspectRatio(0.3333f)
145-
.clickable(
146-
true,
147-
onClick = {
148-
selectFrame(cutFrames[index])
149-
}
150+
Box(
151+
modifier = Modifier.fillMaxSize()
152+
.clickable{
153+
selectFrame(cutFrames[index])
154+
},
155+
contentAlignment = Alignment.Center,
156+
){
157+
DefaultCutFrame(
158+
cutFrame = cutFrames[index],
159+
imageUrlList = listOf(
160+
"file:///android_asset/sample_cut.png".toUri(),
161+
"file:///android_asset/sample_cut.png".toUri(),
162+
"file:///android_asset/sample_cut.png".toUri(),
163+
"file:///android_asset/sample_cut.png".toUri(),
150164
),
151-
painter = painterResource(id = cutFrames[index].frameImageSrc),
152-
contentDescription = cutFrames[index].frameTitle
153-
)
165+
)
166+
Text(
167+
modifier = Modifier.align(Alignment.TopCenter),
168+
text = cutFrames[index].frameTitle,
169+
style = AppTheme.typography.body.copy(fontWeight = FontWeight.Bold),
170+
color = AppTheme.colorScheme.top
171+
)
172+
}
154173
}
155174
}
156175
}

0 commit comments

Comments
 (0)