Skip to content

Commit 8b8b487

Browse files
authored
Merge pull request #41 from YAPP-Github/feat/#38-task-certification
인증샷 촬영 기능 구현
2 parents 971bca7 + af74717 commit 8b8b487

34 files changed

+962
-0
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
implementation(projects.domain)
3636
implementation(projects.feature.login)
3737
implementation(projects.feature.main)
38+
implementation(projects.feature.taskCertification)
3839

3940
// Firebase
4041
implementation(platform(libs.google.firebase.bom))

app/src/main/java/com/yapp/twix/di/FeatureModules.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package com.yapp.twix.di
33
import com.twix.home.di.homeModule
44
import com.twix.login.di.loginModule
55
import com.twix.main.di.mainModule
6+
import com.twix.task_certification.di.taskCertificationModule
67
import org.koin.core.module.Module
78

89
val featureModules: List<Module> =
910
listOf(
1011
loginModule,
1112
mainModule,
1213
homeModule,
14+
taskCertificationModule,
1315
)

core/navigation/src/main/java/com/twix/navigation/NavRoutes.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@ sealed class NavRoutes(
2323
object MainGraph : NavRoutes("main_graph")
2424

2525
object MainRoute : NavRoutes("main")
26+
27+
/**
28+
* TaskCertificationGraph
29+
* */
30+
object TaskCertificationGraph : NavRoutes("task_certification_graph")
31+
32+
object TaskCertificationRoute : NavRoutes("task_certification")
2633
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
plugins {
2+
alias(libs.plugins.twix.feature)
3+
}
4+
5+
android {
6+
namespace = "com.twix.task_certification"
7+
}
8+
dependencies {
9+
implementation(libs.bundles.cameraX)
10+
implementation(libs.guava)
11+
}

feature/task-certification/consumer-rules.pro

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
<uses-permission android:name="android.permission.CAMERA"/>
4+
</manifest>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.twix.task_certification
2+
3+
import android.Manifest
4+
import android.content.pm.PackageManager
5+
import androidx.activity.compose.rememberLauncherForActivityResult
6+
import androidx.activity.result.contract.ActivityResultContracts
7+
import androidx.compose.foundation.background
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Spacer
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.DisposableEffect
14+
import androidx.compose.runtime.LaunchedEffect
15+
import androidx.compose.runtime.getValue
16+
import androidx.compose.runtime.mutableStateOf
17+
import androidx.compose.runtime.remember
18+
import androidx.compose.runtime.rememberCoroutineScope
19+
import androidx.compose.runtime.setValue
20+
import androidx.compose.ui.Alignment
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.tooling.preview.Preview
24+
import androidx.compose.ui.unit.dp
25+
import androidx.core.content.ContextCompat
26+
import androidx.lifecycle.compose.LocalLifecycleOwner
27+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
28+
import com.twix.designsystem.components.text.AppText
29+
import com.twix.designsystem.theme.GrayColor
30+
import com.twix.designsystem.theme.TwixTheme
31+
import com.twix.domain.model.enums.AppTextStyle
32+
import com.twix.task_certification.camera.Camera
33+
import com.twix.task_certification.component.CameraControlBar
34+
import com.twix.task_certification.component.CameraPreviewBox
35+
import com.twix.task_certification.component.TaskCertificationTopBar
36+
import com.twix.task_certification.model.CameraPreview
37+
import com.twix.task_certification.model.TaskCertificationIntent
38+
import com.twix.task_certification.model.TaskCertificationUiState
39+
import kotlinx.coroutines.launch
40+
import org.koin.androidx.compose.koinViewModel
41+
import org.koin.compose.koinInject
42+
43+
@Composable
44+
fun TaskCertificationRoute(
45+
camera: Camera = koinInject(),
46+
viewModel: TaskCertificationViewModel = koinViewModel(),
47+
navigateToBack: () -> Unit,
48+
) {
49+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
50+
val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle()
51+
52+
val lifecycleOwner = LocalLifecycleOwner.current
53+
val coroutineScope = rememberCoroutineScope()
54+
val context = androidx.compose.ui.platform.LocalContext.current
55+
56+
var hasPermission by remember {
57+
mutableStateOf(
58+
ContextCompat.checkSelfPermission(
59+
context,
60+
Manifest.permission.CAMERA,
61+
) == PackageManager.PERMISSION_GRANTED,
62+
)
63+
}
64+
65+
val permissionLauncher =
66+
rememberLauncherForActivityResult(
67+
ActivityResultContracts.RequestPermission(),
68+
) { granted ->
69+
hasPermission = granted
70+
}
71+
72+
LaunchedEffect(Unit) {
73+
if (!hasPermission) {
74+
permissionLauncher.launch(Manifest.permission.CAMERA)
75+
}
76+
}
77+
78+
DisposableEffect(lifecycleOwner, uiState.lens, hasPermission) {
79+
if (hasPermission) {
80+
coroutineScope.launch {
81+
camera.bind(lifecycleOwner, uiState.lens)
82+
}
83+
}
84+
85+
onDispose {
86+
camera.unbind()
87+
}
88+
}
89+
90+
LaunchedEffect(uiState.torch, hasPermission) {
91+
if (hasPermission) {
92+
camera.toggleTorch(uiState.torch)
93+
}
94+
}
95+
96+
TaskCertificationScreen(
97+
uiState = uiState,
98+
cameraPreview = cameraPreview,
99+
onClickClose = navigateToBack,
100+
onCaptureClick = {
101+
if (!hasPermission) return@TaskCertificationScreen
102+
103+
coroutineScope.launch {
104+
camera
105+
.takePicture()
106+
.onSuccess {
107+
viewModel.dispatch(TaskCertificationIntent.TakePicture(it))
108+
}
109+
}
110+
},
111+
onToggleCameraClick = {
112+
viewModel.dispatch(TaskCertificationIntent.ToggleLens)
113+
},
114+
onClickFlash = {
115+
viewModel.dispatch(TaskCertificationIntent.ToggleTorch)
116+
},
117+
)
118+
}
119+
120+
@Composable
121+
private fun TaskCertificationScreen(
122+
uiState: TaskCertificationUiState,
123+
cameraPreview: CameraPreview?,
124+
onClickClose: () -> Unit,
125+
onCaptureClick: () -> Unit,
126+
onToggleCameraClick: () -> Unit,
127+
onClickFlash: () -> Unit,
128+
) {
129+
Column(
130+
Modifier
131+
.fillMaxSize()
132+
.background(GrayColor.C500),
133+
horizontalAlignment = Alignment.CenterHorizontally,
134+
) {
135+
TaskCertificationTopBar(
136+
onClickClose = onClickClose,
137+
)
138+
139+
Spacer(modifier = Modifier.height(24.26.dp))
140+
141+
AppText(
142+
text = stringResource(R.string.task_certification_title),
143+
style = AppTextStyle.H2,
144+
color = GrayColor.C100,
145+
)
146+
147+
Spacer(modifier = Modifier.height(40.dp))
148+
149+
CameraPreviewBox(
150+
showTorch = uiState.showTorch,
151+
capture = uiState.capture,
152+
previewRequest = cameraPreview,
153+
torch = uiState.torch,
154+
onClickFlash = { onClickFlash() },
155+
)
156+
157+
Spacer(modifier = Modifier.height(52.dp))
158+
159+
CameraControlBar(
160+
onCaptureClick = onCaptureClick,
161+
onToggleCameraClick = onToggleCameraClick,
162+
)
163+
}
164+
}
165+
166+
@Preview
167+
@Composable
168+
fun TaskCertificationScreenPreview() {
169+
TwixTheme {
170+
TaskCertificationScreen(
171+
uiState = TaskCertificationUiState(),
172+
cameraPreview = null,
173+
onClickClose = {},
174+
onCaptureClick = {},
175+
onToggleCameraClick = {},
176+
onClickFlash = {},
177+
)
178+
}
179+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.twix.task_certification
2+
3+
import android.net.Uri
4+
import androidx.lifecycle.viewModelScope
5+
import com.twix.task_certification.model.TaskCertificationIntent
6+
import com.twix.task_certification.model.TaskCertificationSideEffect
7+
import com.twix.task_certification.model.TaskCertificationUiState
8+
import com.twix.ui.base.BaseViewModel
9+
import kotlinx.coroutines.launch
10+
11+
class TaskCertificationViewModel :
12+
BaseViewModel<TaskCertificationUiState, TaskCertificationIntent, TaskCertificationSideEffect>(
13+
TaskCertificationUiState(),
14+
) {
15+
override suspend fun handleIntent(intent: TaskCertificationIntent) {
16+
when (intent) {
17+
is TaskCertificationIntent.TakePicture -> {
18+
reducePicture(intent.uri)
19+
}
20+
21+
is TaskCertificationIntent.ToggleLens -> {
22+
reduceLens()
23+
}
24+
25+
is TaskCertificationIntent.ToggleTorch -> {
26+
reduceTorch()
27+
}
28+
}
29+
}
30+
31+
private fun reducePicture(uri: Uri?) {
32+
uri?.let {
33+
reduce { updatePicture(uri) }
34+
} ?: run { onFailureCapture() }
35+
}
36+
37+
private fun onFailureCapture() {
38+
viewModelScope.launch {
39+
emitSideEffect(TaskCertificationSideEffect.ShowImageCaptureFailToast)
40+
}
41+
}
42+
43+
private fun reduceLens() {
44+
reduce { toggleLens() }
45+
}
46+
47+
private fun reduceTorch() {
48+
reduce { toggleTorch() }
49+
}
50+
}

0 commit comments

Comments
 (0)