From 084eb6af457399659cfd68b03461c734f678f7a1 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 01:27:59 +0900 Subject: [PATCH 01/58] =?UTF-8?q?=E2=9E=95=20Build:=20CameraX=20dependency?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/task-certification/.gitignore | 1 + feature/task-certification/build.gradle.kts | 10 ++++++++++ gradle/libs.versions.toml | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 feature/task-certification/.gitignore create mode 100644 feature/task-certification/build.gradle.kts diff --git a/feature/task-certification/.gitignore b/feature/task-certification/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/task-certification/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/task-certification/build.gradle.kts b/feature/task-certification/build.gradle.kts new file mode 100644 index 00000000..11a026f3 --- /dev/null +++ b/feature/task-certification/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + alias(libs.plugins.twix.feature) +} + +android { + namespace = "com.peto.task_certification" +} +dependencies { + implementation(libs.bundles.cameraX) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fcfaa913..878a8164 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,11 +57,22 @@ jetbrains-kotlin-jvm = "2.1.0" # Logging kermit = "2.0.8" +junit = "4.13.2" +espressoCore = "3.7.0" +appcompat = "1.7.1" +cameraX = "1.5.2" +cameraLifecycle = "1.5.2" [libraries] # AndroidX androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } + +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraX" } +androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraX" } +androidx-camera-compose = { module = "androidx.camera:camera-compose", version.ref = "cameraX" } + android-gradle-plugin = { group = "com.android.tools.build", name = "gradle-api", version.ref = "agp" } compose-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } @@ -126,6 +137,9 @@ kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.re # Logging kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } [bundles] androidx = [ @@ -133,6 +147,13 @@ androidx = [ "androidx-lifecycle-runtime-ktx", ] +cameraX = [ + "androidx-camera-camera2", + "androidx-camera-lifecycle", + "androidx-camera-view", + "androidx-camera-compose" +] + ktor = [ "ktor-client-okhttp", "ktor-client-content-negotiation", From 3597ab51592da225b9f68564176a81f267ca03b9 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 01:29:24 +0900 Subject: [PATCH 02/58] =?UTF-8?q?=E2=9C=A8Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=ED=99=9C=EC=98=81=20=EB=B2=84=ED=8A=BC,=20?= =?UTF-8?q?=EB=A0=8C=EC=A6=88=20=EC=A0=84=ED=9B=84=EB=A9=B4=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 92 ++++++++++++++++++ .../src/main/res/drawable/btn.png | Bin 0 -> 6755 bytes .../main/res/drawable/ic_camara_rotate.xml | 13 +++ .../main/res/drawable/ic_camera_shutter.xml | 17 ++++ .../main/res/drawable/ic_camera_toggle.xml | 16 +++ 5 files changed, 138 insertions(+) create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt create mode 100644 feature/task-certification/src/main/res/drawable/btn.png create mode 100644 feature/task-certification/src/main/res/drawable/ic_camara_rotate.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_shutter.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_toggle.xml diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt new file mode 100644 index 00000000..046d3022 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt @@ -0,0 +1,92 @@ +package com.peto.task_certification.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.peto.task_certification.R +import com.twix.designsystem.theme.GrayColor + +@Composable +internal fun CameraControlBar( + modifier: Modifier = Modifier, + onCaptureClick: () -> Unit, + onToggleCameraClick: () -> Unit, +) { + Row( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 41.dp) + .padding(bottom = 115.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + modifier + .size(52.dp) + .clip(RoundedCornerShape(5.dp)) + .background(GrayColor.C300) + .border( + width = 2.dp, + color = GrayColor.C300, + shape = RoundedCornerShape(5.dp), + ), + ) { + Image( + painter = painterResource(R.drawable.btn), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxSize() + .clip(RoundedCornerShape(3.dp)), + ) + } + + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_camera_shutter), + contentDescription = "카메라 촬영 버튼", + modifier = + Modifier.clickable { + onCaptureClick() + }, + ) + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_camera_toggle), + contentDescription = "카메라 토글 버튼", + modifier = + Modifier.clickable { + onToggleCameraClick() + }, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun CameraControlBarPreview() { + CameraControlBar( + onCaptureClick = {}, + onToggleCameraClick = {}, + ) +} diff --git a/feature/task-certification/src/main/res/drawable/btn.png b/feature/task-certification/src/main/res/drawable/btn.png new file mode 100644 index 0000000000000000000000000000000000000000..0b1fe8332fe6782d7d3220484298aa18946442ad GIT binary patch literal 6755 zcmV-p8l2^cP)2g%D2PyTs1~GFv|wv1t);cKzP`6wu`YR5adoY#*xGja z9I8*PR&7hA+FJ1mihu(U1_6;Fkc7<1O>%R`bIyI=-Y1Dw`u}^qkd=FH&hXpQx4-@U zjtif8tzEme$m{hkb-7$;^J5)bQ|NVu;`Gzty(wAMj%l{zYCiaQ&J!Yi=9;Jo4t=d+*(i;b;WCE({HJ!_1~(<P(Mnmwp^RUu`SU6`M7(IBs9yB+ffbDXrH#$4oA@96U8xipNk+pKD98-?w)|0ql znwb9;0WH^`!O!QWC8A8a}OSU>^Zb_wWA~!Ls2w> zqbHkD;PYW(VHC%YAH|VVEoeQ_jLlo#!m_I_!`zE6L4W_ibe4%^)>_wn_uW^=g&r5f zrdJnYD4oQy6HVyv>4Awn7vqCT&t$U(Ke^#{yU^3ygW^~e4GrVb)N~TvUELCTSd5U% z<;LK^0A|gejj3l$#r{3}am(seNaiGRas|Ncqxm#0l}w_PhFgXQuWa0e>u&lk9p+WX z$xmL6=XI~2I}0~nIv=}^w_(wQO04?rpU`;ZV?6)(Lzp#x4xW4AbyVKF zwk??a2?m1j@J28UMkmc983ou}jF&l`g~{|XTqe987uQLOmo12ECgYNb*`X zqin)p{kYO5osyJbArK1iJP&$1d(qV!M<^76kDr77081^8izn4%)kQPl^+oXTk5*&D zi_hZV(PJ>^;Al96F*TK}*)-~_%13Hxx_cO&;iLC5Of55+bUeh=@i7(Ty@cP#JPrnf zys3eJ->pU_Qz_U?9fMx?5=cxdi>m4h6c-hssIW-WP-;ca7k`_k#Z1d0mA2rI2AI}y ztp4tgQCBw=M~@z79r7A9Izvav>)N@~F{YtG4NoKzIB&*mlz44KB7WTRjT=x@TulF3 z2>Fc>gX-K|utIc>(MV@A$Ys)ca}OgT-jhYi&q!{|qWm;0&-cm1kfw##=1^CUc9>H~AT7v($^%jJK0mNB*u}BzRL$L~l zRh8KA=hx7&>kzsVNt`>S3SFrz4!3pVJ3sg)Qt@tN(;1W%k9L`}B|`eiWI^W`z8n{v zvrLQ_DT{Fr*NFUBmc@{}6@qj0q!=!RE5{WV6~aeI4%2uSj}3<+G$2cd@cMZR$xMQF zvEW%hZJlv^=g0qv#n-IF?RVXcJ$v`zjvwC+FP)WArvkm*eK_-+GvMZ$di&c5Rwwbu z(;IQuzkLT|X>5OdknkU3%lM=(xkniQ7iVR1S~nhsFwf8{Dsk~1!-3Teee6d6U0AHlvx^is%Srksm)n3t4g-e+9| z6<-j!l=Ph(qLq^nj+D|^0}~19n_hhtEytTsT{BJ{mmvK6S?qJ?&Be|woA8qrS1AY_ zIaGm1-`bB8z5N)&TFS)xKo=7sGcbG}cnC}*FJsZ7MQarlatS2auTk7EsVeDYGnzzY>JTshLR~7 z7sZi-2XKOodOTTyk0@ubZgXrM1$7fJxx5IqeiLme121gfO)&D~h9zG_AA96^vu7a7 zdj|u4Hr!?$ZSDEALGDysi`G`SX5F}$O1!h1%z$4YpR@FY4FB%#9YjHY0uh%Vg@pAe zJrrYv17rZ9Y!*RAv5=1Nv2F}rbBK(gWZGyyio?@<}PF!he&<)!W${UVKRk1hdx4oUmRQBcnep2=^7l|e~?+= z#YZPQJ{zGy!exdLVy;VMTnwd$M$xNz0)g}#zaSQCBDJHDUeEDw7QEy;2BR~WN@`TK z!O)mA(^q;|lFrDchp=PYJD7RtQnWA{0?h{x9m3mhZow5_xB|21UVxYX@)s=s%1Z5f zwc~0q^>fpZ85~5UVJa3ayB617dkqfl-HToO_md8_pz*`Q_}$Yl5c>O&7#u>@@HsU- zntXXdBH>ydZHRp9FaR23vZy13;w*cPi^U$DdoJQT_P`iZilML%R@e(~Q3Zy0?hqH- z($5CnK}bDxlr?6fvZ@;IyuB55=P$$r*3RZPHX%fqP7Dp8x334sPqb)8G>jjQ_RbDW zK4S{9>~R4`#~d1BisuxRUfj45=bdv7cGIv}VIfYnwd22@dl5e*8;R9SQZrm+*@_XK zfs^EM2?YhI9k=Z?D?zjzqvrFFbY>lxwi&rp{{T6d!Jgt{%@~M8qbMycMM+r+;XB4g z>}S7XT`_tQ_LlzcIM&_&09JnOdvuzC`HL1}=es-5*tn0JZwd|_If|Zm99`Yrc<2}F zk!5tmnO>eR_qA!1MBKwVs;#LZ<%!}e%a>xxqzU{wk3G%(4s4GE$Yf!A4X+lxk#~<8 zV>46%U%(-XtSK)S>So%7dBKpd2Z3EewlE2uMwo($!34*T0TykFzXvhU-H!;_!_7B+ z9i7elQAbky=4-E^H{Oo}1cgbHCt)x9(6(*carV@yOwlxkQfaiDIz{BoD&UyBzkq^7 z?)4X+#&eJV0ykW@3_gOw{5f+*6YZ0fW8RxWIiz>wowb?yta>wVyRVk< z>eb&yD^uv`vEx|$`Ne4A`%4#JiiY}He0Akl5GyIcfxUY;2aV^wWFE74ojy#RFcJL& ziBB5qsAYM#=CL+Tw-mPp*U0?G1qtHW^gg+RjEM|0IbAr&bDYQtoe^U)gma0q7%2@^ z7=@7CB@%n&Lx1+U=dg9hHhg{MS8>^;i{a&U z%bCW`Pg%8!nN=>iHw(_7ZxnX2w4?YA#_SugNzu_(pomb5Zps)baI-VKkq_({Lx2P zx^yWb9OiDg;YQ7dtFF8fU;6Sg>}=eLnsMWp1s<-)z}&fWwNDAVC}3|2`-8}e(=+6t zcJ}lL$vd%Hq9fg33=~i63?pNv%rSD9M26`l5j2=iN@4>cLOMYp6h$mntYlRRS#nqS zUXmk2W!*GH3PP-ftNG&-FcWUAHbxuI}`O*O5IB+NE2jzi!8i~Pn_tMJ9E zu0)LthBehVb{O88$F& z(&u2(hyfZIp-vzGvUbfHcvyq?|KuUmRM%<)U$$(C)+5Xe`uh8E?7%LZJ!>I~OG_|) z=4^cY@nK}TI#3qn^@Soa3x>oPErriSNK!=7MlLL#SGMhTdXX!iQ|2tAxBvygOH)sJ zgvTSu=93n|Ye=y|Z!k2JLfNH{qoR@4??TJVnoU@$#k`li`oRmN@z}gA{K0 zFJn1f*~vG<;dgu=APbg(&(G0YKFdX9c#}Hw(Bo1o0st0IOOjEg&_kkgOD7A2BEt@~W<(x5DX;g;{N=Fk?$?%j=;asDhNzIAmqsI4E5w&sHxeM#@KF%{_M z9Ap!mEdCoJ36`jc<1N$Ftnv7K|J)$uZRAX{Sb=5YH7^{Xfa1}=3pfZ=8``HFB(zw- zCTpy?uz>37POSX)oz(5fk~xY;St~DZd>OC5{yHiu$D*UPO()$VY5;Pt1sBc7^5x6X z)7gcGfBh?rpE#M6Xah6B)Y=r8yX-WB@JHGq0~R1+^9je%S#f< zahZ^v6-_}=rvO>NdXz{C zd}j#5v$9W9)RY)15o%Pv8AaSRvCo`upVp_duray!E2 zxY`DD?tK zy8$BD)4zEEUCjMdDyLCv@9aTEc?EOZ)|7Bjt4k)+D5Q2hapF`gzVtF&z`5s5jt)H? zj1*NI!)>AcypB+36RPBwz9hm4 z9V5bFPfsTitdC-S19t9eM2y_*cylYpb7aWVQ8k>EI{OAxK@Irw+F$&tDw#Sx3nwfp zD@Q=!Pvj$0Y98;zLxHZ*;DW?gEazY{Joxi``h$nyl8alh; zOsygm6&0z$0`p?L(h@;B%Nj+!0uEv_C?=@k6|l}i;X;Za^KkhWu7t0k9L4n$5F>-I z(sAuYBTZ5WnDoA$zJ63w;cIH^AYetbL8s~9zLUpLE`}1IgjAnqkVHjbOI1^%kwKUi zF(eY9YQw1A{oqsh`{r%f!x`S~wlv36RCDBbiHK)dyA$L53ZpIs0I4^Tjs+cM<`N*O zsj0`?Y^uWLmVbE#W#Df1p0J{(K(c_3M+gH^HqV)|ZNsJ@0^3!Wbo2ltwyVOn4@6);C*H6EUgNKi?p$3&n$m0E+`&0CIC=_-gF_Nwl z9U@T&M}!!+KhFJJ18;3%TjMeHb}?CXJB9X0VGL15yt1NHV?!-9WTcovq?s|B$V_9=v)N>Bny!S9Z_;4Lo&gOtuT>EK*oV_2fM#>-b z;rOWzJoD1)gkTmbML_2mM+6yUcMs60G!Z67eL2CKrzvrD_x0&W{{3&Q!1?E$L3l35 z3maZTYWo4F`SIK$HpoH3vD=km8i#cxxSIPs8D2wDQ^pagCjlB!n+nF2<8Ry;J8$vD zC|fWWCc#8#S&Xb*qM2kA*K?EX%5UBBX@i9NDC3cuAml#!-+!jCZt4x_TmeDS7e`Tz z_`|xJChU2DqqYobQasl$xg7ug{cmuQ)V}#ej1=Zacipc_jxdZA7b+S9!=owM)zw8q z*-*KAAi}>iNf`=dAD1}c==Ro3#!JJXb+SK$+nB|L`yUdE_{$;OvvX^x|J3-Z$Wo7 z`~l&W0R=2cbDZ9Xh-5`2TyO}<4?a>42`~u17MRTGt{N}OJy%gmg>YUpXC|Ip{}WaJ zPjZqJbd!A}(e7-DiH%415wb~rVr7~_0{@v|?xn{vA`35^&Dm#)(i`bJf^@>q#JDtP z4^MytC2J;|v2_DLwr>QHBRsBvLXGSX{o`xYptGjWQ3R@@c##;$;?+%WD{N=XcrkzGahB-0=0Tsigg{+^$?1z!CNNXt-c1|-W z$!$Z~=GeDuyTa@uiivM*Z`82L)}w6T&R;MaSASt48n|k=+$xEPdXwI7OzVx zO6T`6P4?|S%!mcGSDEB|(WuZOXZFf7cUCODoWorOPLXBEIVQa?2V@!D4C^Oa6d4sj zq{-UK^cfwdWFL>0QAS9f0sC}=_U(R`x$9#;DAaVje8IWoS5X`|+J;rjFGu;95{9@? zHMg$rUJ}njr5~!($t*RzFDlf;rk8~&x07@RG|%%&gCZf>%yMfQy9qE?ES|?6G@xU& z_8!)xi^yg%deMT2gLZHP7rAzh$4EmT7|JO_k(ZyK(3|f4#Gv6PH0mmAWDGX-Wbt4q zT8PgtI3LTt7$%Q%hP%=4H|QvQ@~ zigBMc=Qzrs8q~=l&0thhvNy^uFW*b;2vR8_=Hj!tlyX5qL*1JaZJiF5>DXkHOd3~7 zwlDJYxzxJHl#UrOBQ6FtYL0q*&IP0f*c@Hj3&@)D9)12m7A6l>$e1nzl~8ygl0t3V zc}HW=0-^B|!S{9_;4qe9L}I!qp+$i}ScSu`&Q3i4>wB5D++lZnbe~Di4G2R>i!p{% zPXg6!*a9~Byu+!zLeuO*LtZvYjLV@w4@hlKPkNT-FacpU74o7I1>#bLFCWsI4wn z2)Bm!aeZ{Cvk9Fa-lq~aouL!_sxFDIY-(_MIXE zoc&2ll6gj6KCVtWxT-xI0AU+yq(&_7q-ma=Ti{$$MCN??ZR>7#&UrXPi`}igM7W`7 zf7dNpL@?+V$wF&Lb}3Tajnhn_V z9%@n{&349%ye-c=lp&pWvPvE|Gd6GDoaQc`sDul0r0L$g3{Q@fB{%g2WZRs`L#iPW zk<<`$$q~K$e*R5bhmlvRpQBM?nCymEl!VB*16mKqPc-Y_jn zOz%gOG7@=|?3qearB8h1Wb!1Hp$}nj|A#k<1R_q2Nh2J796<72JSp^L=6SO*VOJKV8qT-HLymS6m{)tXMD4%E=e5&mJ-S zxY+`}oi5J-$)hv!w8{U6M>L1uHVz}RJoL9=#E_BK{{lY#DesU^Zejoc002ovPDHLk FV1nn9+av%0 literal 0 HcmV?d00001 diff --git a/feature/task-certification/src/main/res/drawable/ic_camara_rotate.xml b/feature/task-certification/src/main/res/drawable/ic_camara_rotate.xml new file mode 100644 index 00000000..3d016d50 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camara_rotate.xml @@ -0,0 +1,13 @@ + + + diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_shutter.xml b/feature/task-certification/src/main/res/drawable/ic_camera_shutter.xml new file mode 100644 index 00000000..211c8782 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_shutter.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_toggle.xml b/feature/task-certification/src/main/res/drawable/ic_camera_toggle.xml new file mode 100644 index 00000000..b17b9cf8 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_toggle.xml @@ -0,0 +1,16 @@ + + + + From 4e001bdebbbff499aafc691a04a64502a4f7e713 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 01:30:36 +0900 Subject: [PATCH 03/58] =?UTF-8?q?=E2=9C=A8Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=ED=94=8C=EB=9E=98=EC=8B=9C=20on/off=20SVG=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/ic_camara_flash_off.xml | 18 ++++++++++++++++++ .../main/res/drawable/ic_camera_flash_on.xml | 10 ++++++++++ 2 files changed, 28 insertions(+) create mode 100644 feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml diff --git a/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml b/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml new file mode 100644 index 00000000..e79ea4c0 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml b/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml new file mode 100644 index 00000000..660bd722 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml @@ -0,0 +1,10 @@ + + + From 6f50885e0049c4dc392be351684b0ce322bb8007 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 01:32:02 +0900 Subject: [PATCH 04/58] =?UTF-8?q?=E2=9C=A8Feat:=20cameraX=20=EC=B4=AC?= =?UTF-8?q?=EC=98=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/AndroidManifest.xml | 4 + .../peto/task_certification/camera/Camera.kt | 24 ++++ .../camera/CaptureCamera.kt | 123 ++++++++++++++++++ .../task_certification/model/CameraPreview.kt | 9 ++ 4 files changed, 160 insertions(+) create mode 100644 feature/task-certification/src/main/AndroidManifest.xml create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt diff --git a/feature/task-certification/src/main/AndroidManifest.xml b/feature/task-certification/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3dde0372 --- /dev/null +++ b/feature/task-certification/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt new file mode 100644 index 00000000..0a9c81a2 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt @@ -0,0 +1,24 @@ +package com.peto.task_certification.camera + +import android.net.Uri +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.SurfaceRequest +import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.flow.StateFlow + +interface Camera { + val surfaceRequests: StateFlow + + suspend fun bind( + lifecycleOwner: LifecycleOwner, + lens: CameraSelector, + ) + + suspend fun unbind() + + fun takePicture( + onComplete: (Uri?) -> Unit, + onFailure: (ImageCaptureException) -> Unit, + ) +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt new file mode 100644 index 00000000..ba4dcfc4 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt @@ -0,0 +1,123 @@ +package com.peto.task_certification.camera + +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.Preview +import androidx.camera.core.SurfaceRequest +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.lifecycle.awaitInstance +import androidx.core.content.ContextCompat +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class CaptureCamera( + private val context: Context, +) : Camera { + private val _surfaceRequests = MutableStateFlow(null) + override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() + + private val imageCapture: ImageCapture = + ImageCapture + .Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) + .setFlashMode(ImageCapture.FLASH_MODE_OFF) + .setJpegQuality(JPEG_QUALITY) + .setOutputFormat(ImageCapture.OUTPUT_FORMAT_JPEG) + .build() + + private val preview: Preview = + Preview.Builder().build().apply { + setSurfaceProvider { request -> + _surfaceRequests.value = request + } + } + + override suspend fun bind( + lifecycleOwner: androidx.lifecycle.LifecycleOwner, + lens: CameraSelector, + ) { + val provider = ProcessCameraProvider.awaitInstance(context) + provider.unbindAll() + provider.bindToLifecycle( + lifecycleOwner, + lens, + preview, + imageCapture, + ) + } + + override fun takePicture( + onComplete: (Uri?) -> Unit, + onFailure: (ImageCaptureException) -> Unit, + ) { + val contentValues = contentValues() + val outputOptions = outputFileOptions(contentValues) + + capture( + imageCapture = imageCapture, + outputOptions = outputOptions, + onComplete = onComplete, + onFailure = onFailure, + ) + } + + private fun contentValues(): ContentValues = + ContentValues().apply { + put( + MediaStore.MediaColumns.DISPLAY_NAME, + IMAGE_NAME.format(System.currentTimeMillis()), + ) + put(MediaStore.MediaColumns.MIME_TYPE, IMAGE_MIME_TYPE) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, IMAGE_PATH) + } + } + + private fun outputFileOptions(contentValues: ContentValues): ImageCapture.OutputFileOptions = + ImageCapture.OutputFileOptions + .Builder( + context.contentResolver, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues, + ).build() + + private fun capture( + imageCapture: ImageCapture, + outputOptions: ImageCapture.OutputFileOptions, + onComplete: (Uri?) -> Unit, + onFailure: (ImageCaptureException) -> Unit, + ) { + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(context), + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(result: ImageCapture.OutputFileResults) { + onComplete(result.savedUri) + } + + override fun onError(exception: ImageCaptureException) { + onFailure(exception) + } + }, + ) + } + + override suspend fun unbind() { + ProcessCameraProvider.awaitInstance(context).unbindAll() + } + + companion object Companion { + private const val JPEG_QUALITY = 95 + + private const val IMAGE_MIME_TYPE = "image/jpeg" + private const val IMAGE_NAME = "task_%d" + private const val IMAGE_PATH = "Pictures/TaskCertification" + } +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt new file mode 100644 index 00000000..70469ee1 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt @@ -0,0 +1,9 @@ +package com.peto.task_certification.model + +import androidx.camera.core.SurfaceRequest +import androidx.compose.runtime.Immutable + +@Immutable +data class CameraPreview( + val request: SurfaceRequest, +) From 846a1ef04cc027527fc9958f5cc8fdb49ac55fe2 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 01:35:27 +0900 Subject: [PATCH 05/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=B9=B4=ED=83=88=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 878a8164..196acaeb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,9 @@ compose-ui-test = "1.7.8" # Ktlint ktlint = "14.0.1" +# CameraX +cameraX = "1.5.2" + # ksp ksp = "2.3.1" @@ -60,14 +63,13 @@ kermit = "2.0.8" junit = "4.13.2" espressoCore = "3.7.0" appcompat = "1.7.1" -cameraX = "1.5.2" -cameraLifecycle = "1.5.2" [libraries] # AndroidX androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" } +# CameraX androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraX" } androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraX" } From 7df1e27a868cfff5a651495e0a54ff15ab1a0f83 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 15:19:37 +0900 Subject: [PATCH 06/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20CameraCo?= =?UTF-8?q?ntrolBar=EC=97=90=20noRippleClickable=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 16 ++----- .../component/TaskCertificationTopBar.kt | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt index 046d3022..46475842 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt @@ -3,7 +3,6 @@ package com.peto.task_certification.component import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -24,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.peto.task_certification.R import com.twix.designsystem.theme.GrayColor +import com.twix.ui.extension.noRippleClickable @Composable internal fun CameraControlBar( @@ -65,19 +65,13 @@ internal fun CameraControlBar( Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_shutter), - contentDescription = "카메라 촬영 버튼", - modifier = - Modifier.clickable { - onCaptureClick() - }, + contentDescription = null, + modifier = Modifier.noRippleClickable(onCaptureClick), ) Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_toggle), - contentDescription = "카메라 토글 버튼", - modifier = - Modifier.clickable { - onToggleCameraClick() - }, + contentDescription = null, + modifier = Modifier.noRippleClickable(onToggleCameraClick), ) } } diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt b/feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt new file mode 100644 index 00000000..ac620dc0 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt @@ -0,0 +1,46 @@ +package com.peto.task_certification.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.peto.task_certification.R +import com.twix.designsystem.theme.GrayColor +import com.twix.ui.extension.noRippleClickable + +@Composable +internal fun TaskCertificationTopBar( + onClickClose: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = + modifier + .fillMaxWidth() + .background(color = GrayColor.C500), + ) { + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_close_c100), + contentDescription = null, + modifier = + Modifier + .padding(24.dp) + .align(Alignment.CenterEnd) + .noRippleClickable(onClickClose), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun TaskCertificationTopBarPreview() { + TaskCertificationTopBar({}) +} From 8c2818e102f0ad241271c5278cce9bab0ff63cfa Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 15:52:06 +0900 Subject: [PATCH 07/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=8B=AB=EA=B8=B0=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/ic_close_c100.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 feature/task-certification/src/main/res/drawable/ic_close_c100.xml diff --git a/feature/task-certification/src/main/res/drawable/ic_close_c100.xml b/feature/task-certification/src/main/res/drawable/ic_close_c100.xml new file mode 100644 index 00000000..949784e6 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_close_c100.xml @@ -0,0 +1,12 @@ + + + From b33218eac6377cfdc1840a998411e8f4b5ee8235 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 15:53:18 +0900 Subject: [PATCH 08/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=83=B7=20=EC=B4=AC=EC=9A=A9=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=91=9C=ED=98=84=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../peto/task_certification/model/CaptureStatus.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt new file mode 100644 index 00000000..d3cd7c49 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt @@ -0,0 +1,11 @@ +package com.peto.task_certification.model + +import android.net.Uri + +sealed interface CaptureStatus { + data class Captured( + val uri: Uri, + ) : CaptureStatus + + data object NotCaptured : CaptureStatus +} From d3a4a95fa3e0880aef58ab694fd0e6d22343ece1 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 16:24:33 +0900 Subject: [PATCH 09/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task-certification/src/main/res/drawable/image.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 feature/task-certification/src/main/res/drawable/image.xml diff --git a/feature/task-certification/src/main/res/drawable/image.xml b/feature/task-certification/src/main/res/drawable/image.xml new file mode 100644 index 00000000..fd345544 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/image.xml @@ -0,0 +1,10 @@ + + + From 0fd6cf301db5b5e87bf6f10c78226c1b4fe5e379 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 16:25:06 +0900 Subject: [PATCH 10/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=ED=94=84=EB=A6=AC=EB=B7=B0=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraPreviewBox.kt | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt new file mode 100644 index 00000000..1dc79b61 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt @@ -0,0 +1,71 @@ +package com.peto.task_certification.component + +import androidx.camera.compose.CameraXViewfinder +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.peto.task_certification.model.CameraPreview +import com.peto.task_certification.model.CaptureStatus +import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.TwixTheme + +@Composable +internal fun CameraPreviewBox( + captureStatus: CaptureStatus, + previewRequest: CameraPreview?, +) { + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 5.dp) + .aspectRatio(1f) + .border( + color = GrayColor.C400, + width = 2.dp, + shape = RoundedCornerShape(73.83.dp), + ).clip(RoundedCornerShape(73.83.dp)), + ) { + when (captureStatus) { + CaptureStatus.NotCaptured -> { + previewRequest?.let { + CameraXViewfinder( + surfaceRequest = it.request, + modifier = Modifier.fillMaxSize(), + ) + } + } + + is CaptureStatus.Captured -> { + AsyncImage( + model = captureStatus.uri, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + } + } + } +} + +@Preview +@Composable +private fun CameraPreviewBoxNotCapturedPreview() { + TwixTheme { + CameraPreviewBox( + captureStatus = CaptureStatus.NotCaptured, + previewRequest = null, + ) + } +} From 203fd847260d757a6ad37c488c4311d239ca7899 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 16:31:51 +0900 Subject: [PATCH 11/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=EC=B4=AC=EC=98=81=20=EA=B0=80=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/twix/di/FeatureModules.kt | 2 + .../TaskCertificationScreen.kt | 109 ++++++++++++++++++ .../TaskCertificationViewModel.kt | 80 +++++++++++++ .../di/TaskCertificationModule.kt | 13 +++ .../model/TaskCertificationIntent.kt | 12 ++ .../model/TaskCertificationSideEffect.kt | 7 ++ .../model/TaskCertificationUiState.kt | 12 ++ .../src/main/res/values/strings.xml | 3 + 8 files changed, 238 insertions(+) create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt create mode 100644 feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt create mode 100644 feature/task-certification/src/main/res/values/strings.xml diff --git a/app/src/main/java/com/yapp/twix/di/FeatureModules.kt b/app/src/main/java/com/yapp/twix/di/FeatureModules.kt index 7365a495..5f8c9075 100644 --- a/app/src/main/java/com/yapp/twix/di/FeatureModules.kt +++ b/app/src/main/java/com/yapp/twix/di/FeatureModules.kt @@ -1,5 +1,6 @@ package com.yapp.twix.di +import com.peto.task_certification.di.taskCertificationModule import com.twix.home.di.homeModule import com.twix.login.di.loginModule import com.twix.main.di.mainModule @@ -10,4 +11,5 @@ val featureModules: List = loginModule, mainModule, homeModule, + taskCertificationModule, ) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt new file mode 100644 index 00000000..20f7faf0 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt @@ -0,0 +1,109 @@ +package com.peto.task_certification + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.peto.task_certification.component.CameraControlBar +import com.peto.task_certification.component.CameraPreviewBox +import com.peto.task_certification.component.TaskCertificationTopBar +import com.peto.task_certification.model.TaskCertificationIntent +import com.peto.task_certification.model.TaskCertificationUiState +import com.twix.designsystem.components.text.AppText +import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.LocalStatusBarColor +import com.twix.designsystem.theme.StatusBarStyle +import com.twix.designsystem.theme.TwixTheme +import com.twix.domain.model.enums.AppTextStyle +import org.koin.androidx.compose.koinViewModel + +@Composable +fun TaskCertificationRoute( + viewModel: TaskCertificationViewModel = koinViewModel(), + navigateToBack: () -> Unit +) { + val uiSate by viewModel.uiState.collectAsStateWithLifecycle() + val lifecycleOwner = LocalLifecycleOwner.current + + LaunchedEffect(Unit) { + viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) + } + + TaskCertificationScreen( + uiState = uiSate, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = {}, + ) +} + +@Composable +private fun TaskCertificationScreen( + uiState: TaskCertificationUiState, + onClickClose: () -> Unit, + onCaptureClick: () -> Unit, + onToggleCameraClick: () -> Unit, +) { + Column( + Modifier + .fillMaxSize() + .background(GrayColor.C500), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TaskCertificationTopBar( + onClickClose = { onClickClose() } + ) + + Spacer(modifier = Modifier.height(24.26.dp)) + + AppText( + text = stringResource(R.string.task_certification_title), + style = AppTextStyle.H2, + color = GrayColor.C100 + ) + + Spacer(modifier = Modifier.height(40.dp)) + + CameraPreviewBox( + captureStatus = uiState.capture, + previewRequest = uiState.preview, + ) + + Spacer(modifier = Modifier.height(52.dp)) + + CameraControlBar( + onCaptureClick = onCaptureClick, + onToggleCameraClick = onToggleCameraClick, + ) + } +} + +@Preview +@Composable +fun TaskCertificationScreenPreview() { + TwixTheme { + TaskCertificationScreen( + uiState = TaskCertificationUiState(), + onClickClose = {}, + onCaptureClick = {}, + onToggleCameraClick = {}, + ) + } +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt new file mode 100644 index 00000000..faa13e33 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt @@ -0,0 +1,80 @@ +package com.peto.task_certification + +import android.net.Uri +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewModelScope +import com.peto.task_certification.camera.Camera +import com.peto.task_certification.model.CameraPreview +import com.peto.task_certification.model.CaptureStatus +import com.peto.task_certification.model.TaskCertificationIntent +import com.peto.task_certification.model.TaskCertificationSideEffect +import com.peto.task_certification.model.TaskCertificationUiState +import com.twix.ui.base.BaseViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class TaskCertificationViewModel( + private val camera: Camera, +) : BaseViewModel( + TaskCertificationUiState(), + ) { + init { + camera.surfaceRequests + .map { request -> request?.let { CameraPreview(request) } } + .onEach { preview -> + reduce { copy(preview = preview) } + }.launchIn(viewModelScope) + } + + override suspend fun handleIntent(intent: TaskCertificationIntent) { + when (intent) { + is TaskCertificationIntent.BindCamera -> { + bindCamera(intent.lifecycleOwner) + } + + is TaskCertificationIntent.TakePicture -> { + takePicture() + } + } + } + + private fun takePicture() { + camera.takePicture( + onComplete = { + updateCapturedCImage(it) + unbindCamera() + }, + onFailure = { + onFailureCapture() + }, + ) + } + + private fun onFailureCapture() { + viewModelScope.launch { + emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) + } + } + + private fun unbindCamera() { + viewModelScope.launch { + camera.unbind() + } + } + + private suspend fun bindCamera(lifecycleOwner: LifecycleOwner) { + camera.bind(lifecycleOwner, uiState.value.lens) + } + + private fun updateCapturedCImage(uri: Uri?) { + uri?.let { + reduce { copy(capture = CaptureStatus.Captured(uri)) } + } ?: run { + viewModelScope.launch { + emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) + } + } + } +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt b/feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt new file mode 100644 index 00000000..a6df437f --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt @@ -0,0 +1,13 @@ +package com.peto.task_certification.di + +import com.peto.task_certification.TaskCertificationViewModel +import com.peto.task_certification.camera.Camera +import com.peto.task_certification.camera.CaptureCamera +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +val taskCertificationModule = + module { + viewModelOf(::TaskCertificationViewModel) + factory { CaptureCamera(get()) } + } diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt new file mode 100644 index 00000000..de24a424 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt @@ -0,0 +1,12 @@ +package com.peto.task_certification.model + +import androidx.lifecycle.LifecycleOwner +import com.twix.ui.base.Intent + +sealed interface TaskCertificationIntent : Intent { + data object TakePicture : TaskCertificationIntent + + data class BindCamera( + val lifecycleOwner: LifecycleOwner, + ) : TaskCertificationIntent +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt new file mode 100644 index 00000000..5bec59a6 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt @@ -0,0 +1,7 @@ +package com.peto.task_certification.model + +import com.twix.ui.base.SideEffect + +sealed interface TaskCertificationSideEffect : SideEffect { + data object ImageCaptureFailException : TaskCertificationSideEffect +} diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt new file mode 100644 index 00000000..390ece99 --- /dev/null +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt @@ -0,0 +1,12 @@ +package com.peto.task_certification.model + +import androidx.camera.core.CameraSelector +import androidx.compose.runtime.Immutable +import com.twix.ui.base.State + +@Immutable +data class TaskCertificationUiState( + val capture: CaptureStatus = CaptureStatus.NotCaptured, + val lens: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, + val preview: CameraPreview? = null, +) : State diff --git a/feature/task-certification/src/main/res/values/strings.xml b/feature/task-certification/src/main/res/values/strings.xml new file mode 100644 index 00000000..88875eed --- /dev/null +++ b/feature/task-certification/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + 인증샷 찍기 + From df06bb45bc315da12f027f1abeec28347d358db2 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 16:32:36 +0900 Subject: [PATCH 12/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=95=A0=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?KtlintFormat=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + feature/task-certification/consumer-rules.pro | 0 feature/task-certification/proguard-rules.pro | 21 +++++++++++++ .../TaskCertificationScreen.kt | 30 ++++++++----------- settings.gradle.kts | 1 + 5 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 feature/task-certification/consumer-rules.pro create mode 100644 feature/task-certification/proguard-rules.pro diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 53d48767..241cf0d3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.domain) implementation(projects.feature.login) implementation(projects.feature.main) + implementation(projects.feature.taskCertification) // Firebase implementation(platform(libs.google.firebase.bom)) diff --git a/feature/task-certification/consumer-rules.pro b/feature/task-certification/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/task-certification/proguard-rules.pro b/feature/task-certification/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/task-certification/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt index 20f7faf0..ad4ef16c 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -24,8 +22,6 @@ import com.peto.task_certification.model.TaskCertificationIntent import com.peto.task_certification.model.TaskCertificationUiState import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor -import com.twix.designsystem.theme.LocalStatusBarColor -import com.twix.designsystem.theme.StatusBarStyle import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import org.koin.androidx.compose.koinViewModel @@ -33,7 +29,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun TaskCertificationRoute( viewModel: TaskCertificationViewModel = koinViewModel(), - navigateToBack: () -> Unit + navigateToBack: () -> Unit, ) { val uiSate by viewModel.uiState.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current @@ -42,16 +38,16 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) } - TaskCertificationScreen( - uiState = uiSate, - onClickClose = { - navigateToBack() - }, - onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) - }, - onToggleCameraClick = {}, - ) + TaskCertificationScreen( + uiState = uiSate, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = {}, + ) } @Composable @@ -68,7 +64,7 @@ private fun TaskCertificationScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { TaskCertificationTopBar( - onClickClose = { onClickClose() } + onClickClose = { onClickClose() }, ) Spacer(modifier = Modifier.height(24.26.dp)) @@ -76,7 +72,7 @@ private fun TaskCertificationScreen( AppText( text = stringResource(R.string.task_certification_title), style = AppTextStyle.H2, - color = GrayColor.C100 + color = GrayColor.C100, ) Spacer(modifier = Modifier.height(40.dp)) diff --git a/settings.gradle.kts b/settings.gradle.kts index 29626c00..9ba52aea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,3 +36,4 @@ include(":core:design-system") include(":core:network") include(":core:analytics") include(":feature:main") +include(":feature:task-certification") From 6949feb8b67465678a30e181112846998499ccd8 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 17:15:56 +0900 Subject: [PATCH 13/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20CameraCo?= =?UTF-8?q?ntrolBar=20=EC=BB=AC=ED=8F=AC=EC=A0=80=EB=B8=94=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt index 46475842..a3cd31a5 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt @@ -1,15 +1,12 @@ package com.peto.task_certification.component 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.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -22,7 +19,6 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.peto.task_certification.R -import com.twix.designsystem.theme.GrayColor import com.twix.ui.extension.noRippleClickable @Composable @@ -35,43 +31,34 @@ internal fun CameraControlBar( modifier = modifier .fillMaxWidth() - .padding(horizontal = 41.dp) - .padding(bottom = 115.dp), + .wrapContentHeight() + .padding(horizontal = 41.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Box( + Image( + painter = painterResource(R.drawable.btn), + contentDescription = null, + contentScale = ContentScale.Crop, modifier = - modifier + Modifier .size(52.dp) - .clip(RoundedCornerShape(5.dp)) - .background(GrayColor.C300) - .border( - width = 2.dp, - color = GrayColor.C300, - shape = RoundedCornerShape(5.dp), - ), - ) { - Image( - painter = painterResource(R.drawable.btn), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - Modifier - .fillMaxSize() - .clip(RoundedCornerShape(3.dp)), - ) - } + .clip(RoundedCornerShape(3.dp)), + ) Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_shutter), contentDescription = null, - modifier = Modifier.noRippleClickable(onCaptureClick), + modifier = + Modifier + .noRippleClickable(onCaptureClick), ) Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_toggle), contentDescription = null, - modifier = Modifier.noRippleClickable(onToggleCameraClick), + modifier = + Modifier + .noRippleClickable(onToggleCameraClick), ) } } From 4f7972e5956864864f127d41bbfc2504772ed032 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 19:20:38 +0900 Subject: [PATCH 14/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=BA=A1=EC=B3=90=ED=9B=84=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../peto/task_certification/TaskCertificationViewModel.kt | 2 +- .../task_certification/model/TaskCertificationUiState.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt index faa13e33..0daf0048 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt @@ -70,7 +70,7 @@ class TaskCertificationViewModel( private fun updateCapturedCImage(uri: Uri?) { uri?.let { - reduce { copy(capture = CaptureStatus.Captured(uri)) } + reduce { updateCapturedCImage(uri) } } ?: run { viewModelScope.launch { emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt index 390ece99..ace2d22a 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt @@ -1,5 +1,6 @@ package com.peto.task_certification.model +import android.net.Uri import androidx.camera.core.CameraSelector import androidx.compose.runtime.Immutable import com.twix.ui.base.State @@ -9,4 +10,6 @@ data class TaskCertificationUiState( val capture: CaptureStatus = CaptureStatus.NotCaptured, val lens: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, val preview: CameraPreview? = null, -) : State +) : State { + fun updateCapturedCImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) +} From 215fae3bb15ebdcbfdb5561d8b0a1c5ada0ca194 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 19:21:00 +0900 Subject: [PATCH 15/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=EB=A0=8C=EC=A6=88=20=ED=86=A0=EA=B8=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 34 +++++++++++++------ .../TaskCertificationViewModel.kt | 16 +++++++-- .../model/TaskCertificationIntent.kt | 4 +++ .../model/TaskCertificationUiState.kt | 10 ++++++ 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt index ad4ef16c..ccbcba61 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt @@ -6,10 +6,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -22,6 +24,8 @@ import com.peto.task_certification.model.TaskCertificationIntent import com.peto.task_certification.model.TaskCertificationUiState import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.LocalStatusBarColor +import com.twix.designsystem.theme.StatusBarStyle import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import org.koin.androidx.compose.koinViewModel @@ -38,16 +42,26 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) } - TaskCertificationScreen( - uiState = uiSate, - onClickClose = { - navigateToBack() - }, - onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) - }, - onToggleCameraClick = {}, - ) + CompositionLocalProvider( + LocalStatusBarColor provides + StatusBarStyle( + GrayColor.C500.toArgb(), + false, + ), + ) { + TaskCertificationScreen( + uiState = uiSate, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = { + viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + }, + ) + } } @Composable diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt index 0daf0048..194ee26f 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope import com.peto.task_certification.camera.Camera import com.peto.task_certification.model.CameraPreview -import com.peto.task_certification.model.CaptureStatus import com.peto.task_certification.model.TaskCertificationIntent import com.peto.task_certification.model.TaskCertificationSideEffect import com.peto.task_certification.model.TaskCertificationUiState @@ -37,6 +36,10 @@ class TaskCertificationViewModel( is TaskCertificationIntent.TakePicture -> { takePicture() } + + is TaskCertificationIntent.ToggleCamera -> { + toggleCamera(intent.lifecycleOwner) + } } } @@ -64,8 +67,10 @@ class TaskCertificationViewModel( } } - private suspend fun bindCamera(lifecycleOwner: LifecycleOwner) { - camera.bind(lifecycleOwner, uiState.value.lens) + private fun bindCamera(lifecycleOwner: LifecycleOwner) { + viewModelScope.launch { + camera.bind(lifecycleOwner, uiState.value.lens) + } } private fun updateCapturedCImage(uri: Uri?) { @@ -77,4 +82,9 @@ class TaskCertificationViewModel( } } } + + private fun toggleCamera(lifecycleOwner: LifecycleOwner) { + reduce { toggleLens() } + bindCamera(lifecycleOwner) + } } diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt index de24a424..d70d7d72 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt @@ -9,4 +9,8 @@ sealed interface TaskCertificationIntent : Intent { data class BindCamera( val lifecycleOwner: LifecycleOwner, ) : TaskCertificationIntent + + data class ToggleCamera( + val lifecycleOwner: LifecycleOwner, + ) : TaskCertificationIntent } diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt index ace2d22a..2b7d1b51 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt @@ -11,5 +11,15 @@ data class TaskCertificationUiState( val lens: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, val preview: CameraPreview? = null, ) : State { + fun toggleLens(): TaskCertificationUiState { + val newLens = + if (lens == CameraSelector.DEFAULT_BACK_CAMERA) { + CameraSelector.DEFAULT_FRONT_CAMERA + } else { + CameraSelector.DEFAULT_BACK_CAMERA + } + return copy(lens = newLens) + } + fun updateCapturedCImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) } From 1d1a9331e5d772e09687d4fe1d90bb9f03577e4e Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 19:28:32 +0900 Subject: [PATCH 16/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=98=EB=AA=BB=20=EC=BB=A4=EB=B0=8B=EB=90=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt index ccbcba61..e9bd4f10 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -24,8 +22,6 @@ import com.peto.task_certification.model.TaskCertificationIntent import com.peto.task_certification.model.TaskCertificationUiState import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor -import com.twix.designsystem.theme.LocalStatusBarColor -import com.twix.designsystem.theme.StatusBarStyle import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import org.koin.androidx.compose.koinViewModel @@ -42,26 +38,18 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) } - CompositionLocalProvider( - LocalStatusBarColor provides - StatusBarStyle( - GrayColor.C500.toArgb(), - false, - ), - ) { - TaskCertificationScreen( - uiState = uiSate, - onClickClose = { - navigateToBack() - }, - onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) - }, - onToggleCameraClick = { - viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) - }, - ) - } + TaskCertificationScreen( + uiState = uiSate, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = { + viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + }, + ) } @Composable From 9268343be9d6ae42164522ec1e50005f10217542 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 21:51:58 +0900 Subject: [PATCH 17/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=98=EB=AA=BB=EB=90=9C=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/twix/di/FeatureModules.kt | 2 +- feature/task-certification/build.gradle.kts | 2 +- .../TaskCertificationScreen.kt | 51 ++++++++++++------- .../TaskCertificationViewModel.kt | 14 ++--- .../task_certification/camera/Camera.kt | 2 +- .../camera/CaptureCamera.kt | 2 +- .../component/CameraControlBar.kt | 4 +- .../component/CameraPreviewBox.kt | 6 +-- .../component/TaskCertificationTopBar.kt | 4 +- .../di/TaskCertificationModule.kt | 8 +-- .../task_certification/model/CameraPreview.kt | 2 +- .../task_certification/model/CaptureStatus.kt | 2 +- .../model/TaskCertificationIntent.kt | 2 +- .../model/TaskCertificationSideEffect.kt | 2 +- .../model/TaskCertificationUiState.kt | 4 +- 15 files changed, 60 insertions(+), 47 deletions(-) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/TaskCertificationScreen.kt (67%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/TaskCertificationViewModel.kt (85%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/camera/Camera.kt (92%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/camera/CaptureCamera.kt (98%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/component/CameraControlBar.kt (96%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/component/CameraPreviewBox.kt (93%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/component/TaskCertificationTopBar.kt (94%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/di/TaskCertificationModule.kt (51%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/model/CameraPreview.kt (79%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/model/CaptureStatus.kt (80%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/model/TaskCertificationIntent.kt (90%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/model/TaskCertificationSideEffect.kt (80%) rename feature/task-certification/src/main/java/com/{peto => twix}/task_certification/model/TaskCertificationUiState.kt (84%) diff --git a/app/src/main/java/com/yapp/twix/di/FeatureModules.kt b/app/src/main/java/com/yapp/twix/di/FeatureModules.kt index 5f8c9075..c820c141 100644 --- a/app/src/main/java/com/yapp/twix/di/FeatureModules.kt +++ b/app/src/main/java/com/yapp/twix/di/FeatureModules.kt @@ -1,9 +1,9 @@ package com.yapp.twix.di -import com.peto.task_certification.di.taskCertificationModule import com.twix.home.di.homeModule import com.twix.login.di.loginModule import com.twix.main.di.mainModule +import com.twix.task_certification.di.taskCertificationModule import org.koin.core.module.Module val featureModules: List = diff --git a/feature/task-certification/build.gradle.kts b/feature/task-certification/build.gradle.kts index 11a026f3..09099005 100644 --- a/feature/task-certification/build.gradle.kts +++ b/feature/task-certification/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.peto.task_certification" + namespace = "com.twix.task_certification" } dependencies { implementation(libs.bundles.cameraX) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt similarity index 67% rename from feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index e9bd4f10..0fb284b3 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification +package com.twix.task_certification import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -6,24 +6,29 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.peto.task_certification.component.CameraControlBar -import com.peto.task_certification.component.CameraPreviewBox -import com.peto.task_certification.component.TaskCertificationTopBar -import com.peto.task_certification.model.TaskCertificationIntent -import com.peto.task_certification.model.TaskCertificationUiState import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.LocalStatusBarColor +import com.twix.designsystem.theme.StatusBarStyle import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.task_certification.R +import com.twix.task_certification.component.CameraControlBar +import com.twix.task_certification.component.CameraPreviewBox +import com.twix.task_certification.component.TaskCertificationTopBar +import com.twix.task_certification.model.TaskCertificationIntent +import com.twix.task_certification.model.TaskCertificationUiState import org.koin.androidx.compose.koinViewModel @Composable @@ -31,25 +36,33 @@ fun TaskCertificationRoute( viewModel: TaskCertificationViewModel = koinViewModel(), navigateToBack: () -> Unit, ) { - val uiSate by viewModel.uiState.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(Unit) { viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) } - TaskCertificationScreen( - uiState = uiSate, - onClickClose = { - navigateToBack() - }, - onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) - }, - onToggleCameraClick = { - viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) - }, - ) + CompositionLocalProvider( + LocalStatusBarColor provides + StatusBarStyle( + GrayColor.C500.toArgb(), + false, + ), + ) { + TaskCertificationScreen( + uiState = uiState, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = { + viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + }, + ) + } } @Composable diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt similarity index 85% rename from feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 194ee26f..07c5439f 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -1,13 +1,13 @@ -package com.peto.task_certification +package com.twix.task_certification import android.net.Uri import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope -import com.peto.task_certification.camera.Camera -import com.peto.task_certification.model.CameraPreview -import com.peto.task_certification.model.TaskCertificationIntent -import com.peto.task_certification.model.TaskCertificationSideEffect -import com.peto.task_certification.model.TaskCertificationUiState +import com.twix.task_certification.camera.Camera +import com.twix.task_certification.model.CameraPreview +import com.twix.task_certification.model.TaskCertificationIntent +import com.twix.task_certification.model.TaskCertificationSideEffect +import com.twix.task_certification.model.TaskCertificationUiState import com.twix.ui.base.BaseViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -75,7 +75,7 @@ class TaskCertificationViewModel( private fun updateCapturedCImage(uri: Uri?) { uri?.let { - reduce { updateCapturedCImage(uri) } + reduce { updateCapturedImage(uri) } } ?: run { viewModelScope.launch { emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt similarity index 92% rename from feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 0a9c81a2..aaabefe4 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.camera +package com.twix.task_certification.camera import android.net.Uri import androidx.camera.core.CameraSelector diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt similarity index 98% rename from feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index ba4dcfc4..ae52321d 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.camera +package com.twix.task_certification.camera import android.content.ContentValues import android.content.Context diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt similarity index 96% rename from feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index a3cd31a5..02e66d83 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.component +package com.twix.task_certification.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -18,7 +18,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.peto.task_certification.R +import com.twix.task_certification.R import com.twix.ui.extension.noRippleClickable @Composable diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt similarity index 93% rename from feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt index 1dc79b61..d3a09ad6 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/component/CameraPreviewBox.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.component +package com.twix.task_certification.component import androidx.camera.compose.CameraXViewfinder import androidx.compose.foundation.border @@ -15,10 +15,10 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import com.peto.task_certification.model.CameraPreview -import com.peto.task_certification.model.CaptureStatus import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme +import com.twix.task_certification.model.CameraPreview +import com.twix.task_certification.model.CaptureStatus @Composable internal fun CameraPreviewBox( diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt similarity index 94% rename from feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt index ac620dc0..32c917c3 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/component/TaskCertificationTopBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.component +package com.twix.task_certification.component import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -12,8 +12,8 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.peto.task_certification.R import com.twix.designsystem.theme.GrayColor +import com.twix.task_certification.R import com.twix.ui.extension.noRippleClickable @Composable diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt b/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt similarity index 51% rename from feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt index a6df437f..49f3de41 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/di/TaskCertificationModule.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt @@ -1,8 +1,8 @@ -package com.peto.task_certification.di +package com.twix.task_certification.di -import com.peto.task_certification.TaskCertificationViewModel -import com.peto.task_certification.camera.Camera -import com.peto.task_certification.camera.CaptureCamera +import com.twix.task_certification.TaskCertificationViewModel +import com.twix.task_certification.camera.Camera +import com.twix.task_certification.camera.CaptureCamera import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/CameraPreview.kt similarity index 79% rename from feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/model/CameraPreview.kt index 70469ee1..898e3ad3 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/CameraPreview.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/CameraPreview.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.model +package com.twix.task_certification.model import androidx.camera.core.SurfaceRequest import androidx.compose.runtime.Immutable diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt similarity index 80% rename from feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt index d3cd7c49..32af79ce 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/CaptureStatus.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.model +package com.twix.task_certification.model import android.net.Uri diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt similarity index 90% rename from feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index d70d7d72..a5d2f826 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.model +package com.twix.task_certification.model import androidx.lifecycle.LifecycleOwner import com.twix.ui.base.Intent diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt similarity index 80% rename from feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt index 5bec59a6..e9188a6c 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationSideEffect.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.model +package com.twix.task_certification.model import com.twix.ui.base.SideEffect diff --git a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt similarity index 84% rename from feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt rename to feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index 2b7d1b51..1ca85b79 100644 --- a/feature/task-certification/src/main/java/com/peto/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -1,4 +1,4 @@ -package com.peto.task_certification.model +package com.twix.task_certification.model import android.net.Uri import androidx.camera.core.CameraSelector @@ -21,5 +21,5 @@ data class TaskCertificationUiState( return copy(lens = newLens) } - fun updateCapturedCImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) + fun updateCapturedImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) } From c678149d9f25dadd02a6a4623a71f8a345d38dad Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 22:05:24 +0900 Subject: [PATCH 18/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20TaskCertificationGrap?= =?UTF-8?q?h=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/navigation/NavRoutes.kt | 7 +++++ .../di/TaskCertificationModule.kt | 5 +++ .../navigation/TaskCertificationGraph.kt | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 feature/task-certification/src/main/java/com/twix/task_certification/navigation/TaskCertificationGraph.kt diff --git a/core/navigation/src/main/java/com/twix/navigation/NavRoutes.kt b/core/navigation/src/main/java/com/twix/navigation/NavRoutes.kt index 625bc78e..803c1001 100644 --- a/core/navigation/src/main/java/com/twix/navigation/NavRoutes.kt +++ b/core/navigation/src/main/java/com/twix/navigation/NavRoutes.kt @@ -23,4 +23,11 @@ sealed class NavRoutes( object MainGraph : NavRoutes("main_graph") object MainRoute : NavRoutes("main") + + /** + * TaskCertificationGraph + * */ + object TaskCertificationGraph : NavRoutes("task_certification_graph") + + object TaskCertificationRoute : NavRoutes("task_certification") } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt b/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt index 49f3de41..15f82a47 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/di/TaskCertificationModule.kt @@ -1,13 +1,18 @@ package com.twix.task_certification.di +import com.twix.navigation.NavRoutes +import com.twix.navigation.base.NavGraphContributor import com.twix.task_certification.TaskCertificationViewModel import com.twix.task_certification.camera.Camera import com.twix.task_certification.camera.CaptureCamera +import com.twix.task_certification.navigation.TaskCertificationGraph import org.koin.core.module.dsl.viewModelOf +import org.koin.core.qualifier.named import org.koin.dsl.module val taskCertificationModule = module { viewModelOf(::TaskCertificationViewModel) factory { CaptureCamera(get()) } + single(named(NavRoutes.TaskCertificationRoute.route)) { TaskCertificationGraph } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/navigation/TaskCertificationGraph.kt b/feature/task-certification/src/main/java/com/twix/task_certification/navigation/TaskCertificationGraph.kt new file mode 100644 index 00000000..46b2bfd8 --- /dev/null +++ b/feature/task-certification/src/main/java/com/twix/task_certification/navigation/TaskCertificationGraph.kt @@ -0,0 +1,31 @@ +package com.twix.task_certification.navigation + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import com.twix.navigation.NavRoutes +import com.twix.navigation.base.NavGraphContributor +import com.twix.task_certification.TaskCertificationRoute + +object TaskCertificationGraph : NavGraphContributor { + override val graphRoute: NavRoutes + get() = NavRoutes.TaskCertificationGraph + override val startDestination: String + get() = NavRoutes.TaskCertificationRoute.route + + override fun NavGraphBuilder.registerGraph(navController: NavHostController) { + navigation( + route = graphRoute.route, + startDestination = startDestination, + ) { + composable(NavRoutes.TaskCertificationRoute.route) { + TaskCertificationRoute( + navigateToBack = { + navController.popBackStack() + }, + ) + } + } + } +} From 4146605ccc844f5afe1538451d03c75b526867d8 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Mon, 26 Jan 2026 22:13:26 +0900 Subject: [PATCH 19/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=AF=B8=EC=99=84=EC=84=B1=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 0fb284b3..30686d2d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -19,8 +17,6 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor -import com.twix.designsystem.theme.LocalStatusBarColor -import com.twix.designsystem.theme.StatusBarStyle import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.task_certification.R @@ -43,26 +39,18 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) } - CompositionLocalProvider( - LocalStatusBarColor provides - StatusBarStyle( - GrayColor.C500.toArgb(), - false, - ), - ) { - TaskCertificationScreen( - uiState = uiState, - onClickClose = { - navigateToBack() - }, - onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) - }, - onToggleCameraClick = { - viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) - }, - ) - } + TaskCertificationScreen( + uiState = uiState, + onClickClose = { + navigateToBack() + }, + onCaptureClick = { + viewModel.dispatch(TaskCertificationIntent.TakePicture) + }, + onToggleCameraClick = { + viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + }, + ) } @Composable From 5fb89fe01d8fc89a9c5335ccb1de7fc4bd4d8f01 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:12:22 +0900 Subject: [PATCH 20/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20CaptureS?= =?UTF-8?q?tatus=EC=97=90=20@Immutable=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/model/CaptureStatus.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt index 32af79ce..45f95d2a 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/CaptureStatus.kt @@ -1,7 +1,9 @@ package com.twix.task_certification.model import android.net.Uri +import androidx.compose.runtime.Immutable +@Immutable sealed interface CaptureStatus { data class Captured( val uri: Uri, From 5f881aa9b21848d0232d52ea7459160df0d86c9b Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:13:20 +0900 Subject: [PATCH 21/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=94=8C=EB=9E=98=EC=8B=9C=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/ic_camara_flash_off.xml | 18 --------------- .../main/res/drawable/ic_camera_flash_on.xml | 10 --------- .../main/res/drawable/ic_camera_torch_off.xml | 22 +++++++++++++++++++ .../main/res/drawable/ic_camera_torch_on.xml | 14 ++++++++++++ 4 files changed, 36 insertions(+), 28 deletions(-) delete mode 100644 feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml delete mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_torch_off.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_torch_on.xml diff --git a/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml b/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml deleted file mode 100644 index e79ea4c0..00000000 --- a/feature/task-certification/src/main/res/drawable/ic_camara_flash_off.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml b/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml deleted file mode 100644 index 660bd722..00000000 --- a/feature/task-certification/src/main/res/drawable/ic_camera_flash_on.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_torch_off.xml b/feature/task-certification/src/main/res/drawable/ic_camera_torch_off.xml new file mode 100644 index 00000000..db90d65e --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_torch_off.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_torch_on.xml b/feature/task-certification/src/main/res/drawable/ic_camera_torch_on.xml new file mode 100644 index 00000000..c3949a31 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_torch_on.xml @@ -0,0 +1,14 @@ + + + + From 41d90d69d270040209cd147ca95cf02c6fe84932 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:20:34 +0900 Subject: [PATCH 22/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=20=ED=94=8C?= =?UTF-8?q?=EB=9E=98=EC=8B=9C=20On/Off=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/task_certification/camera/Camera.kt | 3 ++ .../camera/CaptureCamera.kt | 30 +++++++++++++++---- .../task_certification/model/TorchStatus.kt | 15 ++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 feature/task-certification/src/main/java/com/twix/task_certification/model/TorchStatus.kt diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index aaabefe4..9c452c52 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -5,6 +5,7 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCaptureException import androidx.camera.core.SurfaceRequest import androidx.lifecycle.LifecycleOwner +import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.StateFlow interface Camera { @@ -21,4 +22,6 @@ interface Camera { onComplete: (Uri?) -> Unit, onFailure: (ImageCaptureException) -> Unit, ) + + fun toggleTorch(torch: TorchStatus) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index ae52321d..6b8f1681 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -5,6 +5,8 @@ import android.content.Context import android.net.Uri import android.os.Build import android.provider.MediaStore +import androidx.camera.core.CameraControl +import androidx.camera.core.CameraInfo import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException @@ -13,6 +15,7 @@ import androidx.camera.core.SurfaceRequest import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat +import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -20,6 +23,9 @@ import kotlinx.coroutines.flow.asStateFlow class CaptureCamera( private val context: Context, ) : Camera { + private var cameraControl: CameraControl? = null + private var cameraInfo: CameraInfo? = null + private val _surfaceRequests = MutableStateFlow(null) override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() @@ -45,12 +51,17 @@ class CaptureCamera( ) { val provider = ProcessCameraProvider.awaitInstance(context) provider.unbindAll() - provider.bindToLifecycle( - lifecycleOwner, - lens, - preview, - imageCapture, - ) + + val camera = + provider.bindToLifecycle( + lifecycleOwner, + lens, + preview, + imageCapture, + ) + + cameraControl = camera.cameraControl + cameraInfo = camera.cameraInfo } override fun takePicture( @@ -113,6 +124,13 @@ class CaptureCamera( ProcessCameraProvider.awaitInstance(context).unbindAll() } + override fun toggleTorch(torch: TorchStatus) { + when (torch) { + TorchStatus.On -> cameraControl?.enableTorch(true) + TorchStatus.Off -> cameraControl?.enableTorch(false) + } + } + companion object Companion { private const val JPEG_QUALITY = 95 diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TorchStatus.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TorchStatus.kt new file mode 100644 index 00000000..622637ff --- /dev/null +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TorchStatus.kt @@ -0,0 +1,15 @@ +package com.twix.task_certification.model + +enum class TorchStatus { + On, + Off, + ; + + companion object { + fun toggle(value: TorchStatus): TorchStatus = + when (value) { + On -> Off + Off -> On + } + } +} From 7a508276ddd69bc6154c13f8ed5a72929af1290b Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:21:18 +0900 Subject: [PATCH 23/58] =?UTF-8?q?=E2=9E=95=20Build:=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/task-certification/build.gradle.kts | 1 + gradle/libs.versions.toml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/task-certification/build.gradle.kts b/feature/task-certification/build.gradle.kts index 09099005..3214a3db 100644 --- a/feature/task-certification/build.gradle.kts +++ b/feature/task-certification/build.gradle.kts @@ -7,4 +7,5 @@ android { } dependencies { implementation(libs.bundles.cameraX) + implementation(libs.guava) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 196acaeb..fb9f244d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -140,8 +140,6 @@ kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.re # Logging kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" } junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } [bundles] androidx = [ From 2b7534efc99c4af597b76c7394617aa46fce7156 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:21:36 +0900 Subject: [PATCH 24/58] =?UTF-8?q?=E2=9E=95=20Build:=20guava=20dependency?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fb9f244d..57f800f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,9 @@ ktlint = "14.0.1" # CameraX cameraX = "1.5.2" +# Guava +guava = "33.5.0-android" + # ksp ksp = "2.3.1" @@ -141,6 +144,9 @@ kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.re kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" } junit = { group = "junit", name = "junit", version.ref = "junit" } +# Guava +guava = { module = "com.google.guava:guava", version.ref = "guava" } + [bundles] androidx = [ "androidx-core-ktx", From 8c61d1a339c57879d0526c048f510b473b872b99 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:22:59 +0900 Subject: [PATCH 25/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20UI=20=EC=B9=B4?= =?UTF-8?q?=EB=A9=94=EB=9D=BC=20=ED=94=8C=EB=9E=98=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 12 ++- .../TaskCertificationViewModel.kt | 10 +++ .../component/CameraPreviewBox.kt | 82 ++++++++++++++----- .../model/TaskCertificationIntent.kt | 4 + .../model/TaskCertificationUiState.kt | 6 ++ 5 files changed, 93 insertions(+), 21 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 30686d2d..18efedd9 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -1,5 +1,6 @@ package com.twix.task_certification +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -19,7 +20,6 @@ import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle -import com.twix.task_certification.R import com.twix.task_certification.component.CameraControlBar import com.twix.task_certification.component.CameraPreviewBox import com.twix.task_certification.component.TaskCertificationTopBar @@ -50,6 +50,10 @@ fun TaskCertificationRoute( onToggleCameraClick = { viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) }, + onClickFlash = { + Log.d("dasdas", "onClickFlash") + viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) + }, ) } @@ -59,6 +63,7 @@ private fun TaskCertificationScreen( onClickClose: () -> Unit, onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, + onClickFlash: () -> Unit, ) { Column( Modifier @@ -81,8 +86,10 @@ private fun TaskCertificationScreen( Spacer(modifier = Modifier.height(40.dp)) CameraPreviewBox( - captureStatus = uiState.capture, + capture = uiState.capture, previewRequest = uiState.preview, + torch = uiState.torch, + onClickFlash = { onClickFlash() }, ) Spacer(modifier = Modifier.height(52.dp)) @@ -103,6 +110,7 @@ fun TaskCertificationScreenPreview() { onClickClose = {}, onCaptureClick = {}, onToggleCameraClick = {}, + onClickFlash = {}, ) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 07c5439f..1a9386ad 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -40,6 +40,10 @@ class TaskCertificationViewModel( is TaskCertificationIntent.ToggleCamera -> { toggleCamera(intent.lifecycleOwner) } + + is TaskCertificationIntent.ToggleFlash -> { + toggleTorch() + } } } @@ -76,6 +80,7 @@ class TaskCertificationViewModel( private fun updateCapturedCImage(uri: Uri?) { uri?.let { reduce { updateCapturedImage(uri) } + reduce { toggleTorch() } } ?: run { viewModelScope.launch { emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) @@ -87,4 +92,9 @@ class TaskCertificationViewModel( reduce { toggleLens() } bindCamera(lifecycleOwner) } + + private fun toggleTorch() { + reduce { toggleTorch() } + camera.toggleTorch(uiState.value.torch) + } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt index d3a09ad6..ded07c56 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt @@ -1,6 +1,7 @@ package com.twix.task_certification.component import androidx.camera.compose.CameraXViewfinder +import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio @@ -11,23 +12,31 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme +import com.twix.task_certification.R import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.CaptureStatus +import com.twix.task_certification.model.TorchStatus +import com.twix.ui.extension.noRippleClickable @Composable -internal fun CameraPreviewBox( - captureStatus: CaptureStatus, +fun CameraPreviewBox( + capture: CaptureStatus, previewRequest: CameraPreview?, + torch: TorchStatus, + onClickFlash: () -> Unit, + modifier: Modifier = Modifier, ) { Box( modifier = - Modifier + modifier .fillMaxWidth() .padding(horizontal = 5.dp) .aspectRatio(1f) @@ -37,35 +46,70 @@ internal fun CameraPreviewBox( shape = RoundedCornerShape(73.83.dp), ).clip(RoundedCornerShape(73.83.dp)), ) { - when (captureStatus) { - CaptureStatus.NotCaptured -> { - previewRequest?.let { - CameraXViewfinder( - surfaceRequest = it.request, - modifier = Modifier.fillMaxSize(), - ) - } - } + CameraSurface(capture, previewRequest) + + if (capture == CaptureStatus.NotCaptured) { + TorchIcon(torch, onClickFlash) + } + } +} - is CaptureStatus.Captured -> { - AsyncImage( - model = captureStatus.uri, - contentDescription = null, +@Composable +private fun CameraSurface( + capture: CaptureStatus, + previewRequest: CameraPreview?, +) { + when (capture) { + CaptureStatus.NotCaptured -> { + previewRequest?.let { + CameraXViewfinder( + surfaceRequest = it.request, modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, ) } } + + is CaptureStatus.Captured -> { + AsyncImage( + model = capture.uri, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + } } } +@Composable +private fun TorchIcon( + torch: TorchStatus, + onClickFlash: () -> Unit, +) { + val flashIcon = + when (torch) { + TorchStatus.On -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_on) + TorchStatus.Off -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_off) + } + + Image( + imageVector = flashIcon, + contentDescription = null, + modifier = + Modifier + .noRippleClickable(onClickFlash) + .padding(start = 30.33.dp, top = 31.82.dp), + ) +} + @Preview @Composable -private fun CameraPreviewBoxNotCapturedPreview() { +fun CameraPreviewBoxNotCapturedPreview() { TwixTheme { CameraPreviewBox( - captureStatus = CaptureStatus.NotCaptured, + capture = CaptureStatus.NotCaptured, + torch = TorchStatus.Off, previewRequest = null, + onClickFlash = {}, ) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index a5d2f826..a833bc61 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -13,4 +13,8 @@ sealed interface TaskCertificationIntent : Intent { data class ToggleCamera( val lifecycleOwner: LifecycleOwner, ) : TaskCertificationIntent + + data class ToggleFlash( + val lifecycleOwner: LifecycleOwner, + ) : TaskCertificationIntent } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index 1ca85b79..cfb0e58d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -8,6 +8,7 @@ import com.twix.ui.base.State @Immutable data class TaskCertificationUiState( val capture: CaptureStatus = CaptureStatus.NotCaptured, + val torch: TorchStatus = TorchStatus.Off, val lens: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, val preview: CameraPreview? = null, ) : State { @@ -21,5 +22,10 @@ data class TaskCertificationUiState( return copy(lens = newLens) } + fun toggleTorch(): TaskCertificationUiState { + val newFlashMode = TorchStatus.toggle(torch) + return copy(torch = newFlashMode) + } + fun updateCapturedImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) } From f2947c752acc0c91da541a25730d467bde5b45e5 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 16:53:15 +0900 Subject: [PATCH 26/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=86=A0=EC=B9=98=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraPreviewBox.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt index ded07c56..96597674 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt @@ -85,14 +85,14 @@ private fun TorchIcon( torch: TorchStatus, onClickFlash: () -> Unit, ) { - val flashIcon = + val torchIcon = when (torch) { TorchStatus.On -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_on) TorchStatus.Off -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_off) } Image( - imageVector = flashIcon, + imageVector = torchIcon, contentDescription = null, modifier = Modifier From 19c23fedcec547697131e75a0b711091c6ddd251 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 21:39:35 +0900 Subject: [PATCH 27/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 18efedd9..e10eb63c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -1,6 +1,5 @@ package com.twix.task_certification -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -51,7 +50,6 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) }, onClickFlash = { - Log.d("dasdas", "onClickFlash") viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) }, ) From 9733fde59fd16fa320a5eb4a212cf7f40ef683f0 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Wed, 28 Jan 2026 02:30:25 +0900 Subject: [PATCH 28/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20lifecycleowner=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 2 +- .../twix/task_certification/model/TaskCertificationIntent.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index e10eb63c..8242ad99 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -50,7 +50,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash) }, ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index a833bc61..cdeae137 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -14,7 +14,5 @@ sealed interface TaskCertificationIntent : Intent { val lifecycleOwner: LifecycleOwner, ) : TaskCertificationIntent - data class ToggleFlash( - val lifecycleOwner: LifecycleOwner, - ) : TaskCertificationIntent + data object ToggleFlash : TaskCertificationIntent } From 900c7cca7a56e9d8f1fc05dba7665a3a2aea5dbe Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 10:46:40 +0900 Subject: [PATCH 29/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EB=B0=94=EC=9D=B8=EB=94=A9=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 15 ++++- .../TaskCertificationViewModel.kt | 56 +++++-------------- .../twix/task_certification/camera/Camera.kt | 4 +- .../camera/CaptureCamera.kt | 8 +-- .../model/TaskCertificationIntent.kt | 4 -- 5 files changed, 31 insertions(+), 56 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 18efedd9..4ed41dfd 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -20,27 +20,34 @@ import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.task_certification.camera.Camera import com.twix.task_certification.component.CameraControlBar import com.twix.task_certification.component.CameraPreviewBox import com.twix.task_certification.component.TaskCertificationTopBar +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationUiState import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject @Composable fun TaskCertificationRoute( + camera: Camera = koinInject(), viewModel: TaskCertificationViewModel = koinViewModel(), navigateToBack: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle() + val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(Unit) { - viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) + LaunchedEffect(uiState.lens) { + camera.bind(lifecycleOwner, uiState.lens) } TaskCertificationScreen( uiState = uiState, + cameraPreview = cameraPreview, onClickClose = { navigateToBack() }, @@ -60,6 +67,7 @@ fun TaskCertificationRoute( @Composable private fun TaskCertificationScreen( uiState: TaskCertificationUiState, + cameraPreview: CameraPreview?, onClickClose: () -> Unit, onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, @@ -87,7 +95,7 @@ private fun TaskCertificationScreen( CameraPreviewBox( capture = uiState.capture, - previewRequest = uiState.preview, + previewRequest = cameraPreview, torch = uiState.torch, onClickFlash = { onClickFlash() }, ) @@ -107,6 +115,7 @@ fun TaskCertificationScreenPreview() { TwixTheme { TaskCertificationScreen( uiState = TaskCertificationUiState(), + cameraPreview = null, onClickClose = {}, onCaptureClick = {}, onToggleCameraClick = {}, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 1a9386ad..de0e0238 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -3,36 +3,18 @@ package com.twix.task_certification import android.net.Uri import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope -import com.twix.task_certification.camera.Camera -import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect import com.twix.task_certification.model.TaskCertificationUiState import com.twix.ui.base.BaseViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -class TaskCertificationViewModel( - private val camera: Camera, -) : BaseViewModel( +class TaskCertificationViewModel : + BaseViewModel( TaskCertificationUiState(), ) { - init { - camera.surfaceRequests - .map { request -> request?.let { CameraPreview(request) } } - .onEach { preview -> - reduce { copy(preview = preview) } - }.launchIn(viewModelScope) - } - override suspend fun handleIntent(intent: TaskCertificationIntent) { when (intent) { - is TaskCertificationIntent.BindCamera -> { - bindCamera(intent.lifecycleOwner) - } - is TaskCertificationIntent.TakePicture -> { takePicture() } @@ -48,15 +30,15 @@ class TaskCertificationViewModel( } private fun takePicture() { - camera.takePicture( - onComplete = { - updateCapturedCImage(it) - unbindCamera() - }, - onFailure = { - onFailureCapture() - }, - ) +// camera.takePicture( +// onComplete = { +// updateCapturedCImage(it) +// unbindCamera() +// }, +// onFailure = { +// onFailureCapture() +// }, +// ) } private fun onFailureCapture() { @@ -65,18 +47,6 @@ class TaskCertificationViewModel( } } - private fun unbindCamera() { - viewModelScope.launch { - camera.unbind() - } - } - - private fun bindCamera(lifecycleOwner: LifecycleOwner) { - viewModelScope.launch { - camera.bind(lifecycleOwner, uiState.value.lens) - } - } - private fun updateCapturedCImage(uri: Uri?) { uri?.let { reduce { updateCapturedImage(uri) } @@ -90,11 +60,11 @@ class TaskCertificationViewModel( private fun toggleCamera(lifecycleOwner: LifecycleOwner) { reduce { toggleLens() } - bindCamera(lifecycleOwner) + // bindCamera(lifecycleOwner) } private fun toggleTorch() { reduce { toggleTorch() } - camera.toggleTorch(uiState.value.torch) + // camera.toggleTorch(uiState.value.torch) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 9c452c52..966d9ac3 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -3,13 +3,13 @@ package com.twix.task_certification.camera import android.net.Uri import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCaptureException -import androidx.camera.core.SurfaceRequest import androidx.lifecycle.LifecycleOwner +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.StateFlow interface Camera { - val surfaceRequests: StateFlow + val surfaceRequests: StateFlow suspend fun bind( lifecycleOwner: LifecycleOwner, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 6b8f1681..965a660e 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -11,10 +11,10 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException import androidx.camera.core.Preview -import androidx.camera.core.SurfaceRequest import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,8 +26,8 @@ class CaptureCamera( private var cameraControl: CameraControl? = null private var cameraInfo: CameraInfo? = null - private val _surfaceRequests = MutableStateFlow(null) - override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() + private val _surfaceRequests = MutableStateFlow(null) + override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() private val imageCapture: ImageCapture = ImageCapture @@ -41,7 +41,7 @@ class CaptureCamera( private val preview: Preview = Preview.Builder().build().apply { setSurfaceProvider { request -> - _surfaceRequests.value = request + _surfaceRequests.value = CameraPreview(request) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index a833bc61..1d143c7e 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -6,10 +6,6 @@ import com.twix.ui.base.Intent sealed interface TaskCertificationIntent : Intent { data object TakePicture : TaskCertificationIntent - data class BindCamera( - val lifecycleOwner: LifecycleOwner, - ) : TaskCertificationIntent - data class ToggleCamera( val lifecycleOwner: LifecycleOwner, ) : TaskCertificationIntent From ac190ddd48df5dbd1a8658ce0a2edc2f1fc9bced Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:03:37 +0900 Subject: [PATCH 30/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EC=B4=AC=EC=98=81=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/twix/main/MainActivity.kt | 5 ++-- .../TaskCertificationScreen.kt | 11 ++++++-- .../TaskCertificationViewModel.kt | 28 ++++--------------- .../model/TaskCertificationIntent.kt | 5 +++- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index ff8c6e45..36dc792e 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.navigation.AppNavHost +import com.twix.task_certification.TaskCertificationRoute class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,7 +22,8 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - AppNavHost() + // AppNavHost() + TaskCertificationRoute {} } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 4ed41dfd..33f82d40 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -1,6 +1,5 @@ package com.twix.task_certification -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -52,13 +51,19 @@ fun TaskCertificationRoute( navigateToBack() }, onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) + camera.takePicture( + onComplete = { + viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) + }, + onFailure = { + // feat/#38-task-certification-gallery branch에서 작업 예정 + }, + ) }, onToggleCameraClick = { viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) }, onClickFlash = { - Log.d("dasdas", "onClickFlash") viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) }, ) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index de0e0238..a5bd171c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -16,7 +16,7 @@ class TaskCertificationViewModel : override suspend fun handleIntent(intent: TaskCertificationIntent) { when (intent) { is TaskCertificationIntent.TakePicture -> { - takePicture() + takePicture(intent.uri) } is TaskCertificationIntent.ToggleCamera -> { @@ -29,16 +29,11 @@ class TaskCertificationViewModel : } } - private fun takePicture() { -// camera.takePicture( -// onComplete = { -// updateCapturedCImage(it) -// unbindCamera() -// }, -// onFailure = { -// onFailureCapture() -// }, -// ) + private fun takePicture(uri: Uri?) { + uri?.let { + reduce { updateCapturedImage(uri) } + reduce { toggleTorch() } + } ?: run { onFailureCapture() } } private fun onFailureCapture() { @@ -47,17 +42,6 @@ class TaskCertificationViewModel : } } - private fun updateCapturedCImage(uri: Uri?) { - uri?.let { - reduce { updateCapturedImage(uri) } - reduce { toggleTorch() } - } ?: run { - viewModelScope.launch { - emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) - } - } - } - private fun toggleCamera(lifecycleOwner: LifecycleOwner) { reduce { toggleLens() } // bindCamera(lifecycleOwner) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index 1d143c7e..8e308ca6 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -1,10 +1,13 @@ package com.twix.task_certification.model +import android.net.Uri import androidx.lifecycle.LifecycleOwner import com.twix.ui.base.Intent sealed interface TaskCertificationIntent : Intent { - data object TakePicture : TaskCertificationIntent + data class TakePicture( + val uri: Uri?, + ) : TaskCertificationIntent data class ToggleCamera( val lifecycleOwner: LifecycleOwner, From 753f3b858050e5472683e288764d9b797fe3b953 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:09:02 +0900 Subject: [PATCH 31/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/yapp/twix/main/MainActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index 36dc792e..ff8c6e45 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.task_certification.TaskCertificationRoute +import com.twix.navigation.AppNavHost class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,8 +22,7 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - // AppNavHost() - TaskCertificationRoute {} + AppNavHost() } } } From 94ea778f16ab2e913434f9e8a2bf1f073a90aeb2 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:17:06 +0900 Subject: [PATCH 32/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EB=A0=8C=EC=A6=88=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EC=B1=85=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/task_certification/TaskCertificationScreen.kt | 2 +- .../twix/task_certification/TaskCertificationViewModel.kt | 8 +++----- .../task_certification/model/TaskCertificationIntent.kt | 4 +--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 33f82d40..14c47955 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -61,7 +61,7 @@ fun TaskCertificationRoute( ) }, onToggleCameraClick = { - viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index a5bd171c..d4c1da15 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -1,7 +1,6 @@ package com.twix.task_certification import android.net.Uri -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect @@ -19,8 +18,8 @@ class TaskCertificationViewModel : takePicture(intent.uri) } - is TaskCertificationIntent.ToggleCamera -> { - toggleCamera(intent.lifecycleOwner) + is TaskCertificationIntent.ToggleLens -> { + toggleLens() } is TaskCertificationIntent.ToggleFlash -> { @@ -42,9 +41,8 @@ class TaskCertificationViewModel : } } - private fun toggleCamera(lifecycleOwner: LifecycleOwner) { + private fun toggleLens() { reduce { toggleLens() } - // bindCamera(lifecycleOwner) } private fun toggleTorch() { diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index 8e308ca6..c9d1bd46 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -9,9 +9,7 @@ sealed interface TaskCertificationIntent : Intent { val uri: Uri?, ) : TaskCertificationIntent - data class ToggleCamera( - val lifecycleOwner: LifecycleOwner, - ) : TaskCertificationIntent + data object ToggleLens : TaskCertificationIntent data class ToggleFlash( val lifecycleOwner: LifecycleOwner, From 8deb07269a578db8968ccd43e0f8dd9ffdc90c66 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:48:47 +0900 Subject: [PATCH 33/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=B4=AC=EC=98=81=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=BD=9C=EB=B0=B1=EC=97=90=EC=84=9C=20suspend?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 20 +++--- .../twix/task_certification/camera/Camera.kt | 6 +- .../camera/CaptureCamera.kt | 64 +++++++++---------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 14c47955..3fbe55fa 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.coroutineScope import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme @@ -26,6 +27,7 @@ import com.twix.task_certification.component.TaskCertificationTopBar import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationUiState +import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @@ -39,6 +41,7 @@ fun TaskCertificationRoute( val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current + val coroutineScope = lifecycleOwner.lifecycle.coroutineScope LaunchedEffect(uiState.lens) { camera.bind(lifecycleOwner, uiState.lens) @@ -51,14 +54,15 @@ fun TaskCertificationRoute( navigateToBack() }, onCaptureClick = { - camera.takePicture( - onComplete = { - viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) - }, - onFailure = { - // feat/#38-task-certification-gallery branch에서 작업 예정 - }, - ) + coroutineScope.launch { + camera + .takePicture() + .onSuccess { + viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) + }.onFailure { + // feat/#38-task-certification-gallery branch에서 작업 예정 + } + } }, onToggleCameraClick = { viewModel.dispatch(TaskCertificationIntent.ToggleLens) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 966d9ac3..1507612f 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -2,7 +2,6 @@ package com.twix.task_certification.camera import android.net.Uri import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageCaptureException import androidx.lifecycle.LifecycleOwner import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus @@ -18,10 +17,7 @@ interface Camera { suspend fun unbind() - fun takePicture( - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) + suspend fun takePicture(): Result fun toggleTorch(torch: TorchStatus) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 965a660e..70caa956 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -19,6 +19,10 @@ import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class CaptureCamera( private val context: Context, @@ -64,20 +68,17 @@ class CaptureCamera( cameraInfo = camera.cameraInfo } - override fun takePicture( - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) { - val contentValues = contentValues() - val outputOptions = outputFileOptions(contentValues) - - capture( - imageCapture = imageCapture, - outputOptions = outputOptions, - onComplete = onComplete, - onFailure = onFailure, - ) - } + override suspend fun takePicture(): Result = + suspendCancellableCoroutine { continuation -> + val contentValues = contentValues() + val outputOptions = outputFileOptions(contentValues) + + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(context), + capture(continuation), + ) + } private fun contentValues(): ContentValues = ContentValues().apply { @@ -99,26 +100,23 @@ class CaptureCamera( contentValues, ).build() - private fun capture( - imageCapture: ImageCapture, - outputOptions: ImageCapture.OutputFileOptions, - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) { - imageCapture.takePicture( - outputOptions, - ContextCompat.getMainExecutor(context), - object : ImageCapture.OnImageSavedCallback { - override fun onImageSaved(result: ImageCapture.OutputFileResults) { - onComplete(result.savedUri) + private fun capture(continuation: Continuation>): ImageCapture.OnImageSavedCallback = + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(result: ImageCapture.OutputFileResults) { + val uri = result.savedUri + if (uri != null) { + continuation.resume(Result.success(uri)) + } else { + continuation.resume( + Result.failure(IllegalStateException(URI_NOT_FOUND_EXCEPTION)), + ) } + } - override fun onError(exception: ImageCaptureException) { - onFailure(exception) - } - }, - ) - } + override fun onError(exception: ImageCaptureException) { + continuation.resumeWithException(exception) + } + } override suspend fun unbind() { ProcessCameraProvider.awaitInstance(context).unbindAll() @@ -137,5 +135,7 @@ class CaptureCamera( private const val IMAGE_MIME_TYPE = "image/jpeg" private const val IMAGE_NAME = "task_%d" private const val IMAGE_PATH = "Pictures/TaskCertification" + + private const val URI_NOT_FOUND_EXCEPTION = "촬영한 이미지의 Uri를 찾을 수 없습니다" } } From 7cf1a78daeb2b987c0e3040a03347b2d00f0b7af Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:51:32 +0900 Subject: [PATCH 34/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor::=20Lifecyc?= =?UTF-8?q?leOwner=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/camera/CaptureCamera.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 70caa956..c830434c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -14,6 +14,7 @@ import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow @@ -50,7 +51,7 @@ class CaptureCamera( } override suspend fun bind( - lifecycleOwner: androidx.lifecycle.LifecycleOwner, + lifecycleOwner: LifecycleOwner, lens: CameraSelector, ) { val provider = ProcessCameraProvider.awaitInstance(context) From 326c781753a5d199d29267ca3d8f5aa0f0867444 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 10:46:40 +0900 Subject: [PATCH 35/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EB=B0=94=EC=9D=B8=EB=94=A9=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 15 ++++- .../TaskCertificationViewModel.kt | 56 +++++-------------- .../twix/task_certification/camera/Camera.kt | 4 +- .../camera/CaptureCamera.kt | 8 +-- .../model/TaskCertificationIntent.kt | 4 -- 5 files changed, 31 insertions(+), 56 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 8242ad99..5545afe1 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -19,27 +19,34 @@ import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.task_certification.camera.Camera import com.twix.task_certification.component.CameraControlBar import com.twix.task_certification.component.CameraPreviewBox import com.twix.task_certification.component.TaskCertificationTopBar +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationUiState import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject @Composable fun TaskCertificationRoute( + camera: Camera = koinInject(), viewModel: TaskCertificationViewModel = koinViewModel(), navigateToBack: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle() + val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(Unit) { - viewModel.dispatch(TaskCertificationIntent.BindCamera(lifecycleOwner)) + LaunchedEffect(uiState.lens) { + camera.bind(lifecycleOwner, uiState.lens) } TaskCertificationScreen( uiState = uiState, + cameraPreview = cameraPreview, onClickClose = { navigateToBack() }, @@ -58,6 +65,7 @@ fun TaskCertificationRoute( @Composable private fun TaskCertificationScreen( uiState: TaskCertificationUiState, + cameraPreview: CameraPreview?, onClickClose: () -> Unit, onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, @@ -85,7 +93,7 @@ private fun TaskCertificationScreen( CameraPreviewBox( capture = uiState.capture, - previewRequest = uiState.preview, + previewRequest = cameraPreview, torch = uiState.torch, onClickFlash = { onClickFlash() }, ) @@ -105,6 +113,7 @@ fun TaskCertificationScreenPreview() { TwixTheme { TaskCertificationScreen( uiState = TaskCertificationUiState(), + cameraPreview = null, onClickClose = {}, onCaptureClick = {}, onToggleCameraClick = {}, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 1a9386ad..de0e0238 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -3,36 +3,18 @@ package com.twix.task_certification import android.net.Uri import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope -import com.twix.task_certification.camera.Camera -import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect import com.twix.task_certification.model.TaskCertificationUiState import com.twix.ui.base.BaseViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -class TaskCertificationViewModel( - private val camera: Camera, -) : BaseViewModel( +class TaskCertificationViewModel : + BaseViewModel( TaskCertificationUiState(), ) { - init { - camera.surfaceRequests - .map { request -> request?.let { CameraPreview(request) } } - .onEach { preview -> - reduce { copy(preview = preview) } - }.launchIn(viewModelScope) - } - override suspend fun handleIntent(intent: TaskCertificationIntent) { when (intent) { - is TaskCertificationIntent.BindCamera -> { - bindCamera(intent.lifecycleOwner) - } - is TaskCertificationIntent.TakePicture -> { takePicture() } @@ -48,15 +30,15 @@ class TaskCertificationViewModel( } private fun takePicture() { - camera.takePicture( - onComplete = { - updateCapturedCImage(it) - unbindCamera() - }, - onFailure = { - onFailureCapture() - }, - ) +// camera.takePicture( +// onComplete = { +// updateCapturedCImage(it) +// unbindCamera() +// }, +// onFailure = { +// onFailureCapture() +// }, +// ) } private fun onFailureCapture() { @@ -65,18 +47,6 @@ class TaskCertificationViewModel( } } - private fun unbindCamera() { - viewModelScope.launch { - camera.unbind() - } - } - - private fun bindCamera(lifecycleOwner: LifecycleOwner) { - viewModelScope.launch { - camera.bind(lifecycleOwner, uiState.value.lens) - } - } - private fun updateCapturedCImage(uri: Uri?) { uri?.let { reduce { updateCapturedImage(uri) } @@ -90,11 +60,11 @@ class TaskCertificationViewModel( private fun toggleCamera(lifecycleOwner: LifecycleOwner) { reduce { toggleLens() } - bindCamera(lifecycleOwner) + // bindCamera(lifecycleOwner) } private fun toggleTorch() { reduce { toggleTorch() } - camera.toggleTorch(uiState.value.torch) + // camera.toggleTorch(uiState.value.torch) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 9c452c52..966d9ac3 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -3,13 +3,13 @@ package com.twix.task_certification.camera import android.net.Uri import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCaptureException -import androidx.camera.core.SurfaceRequest import androidx.lifecycle.LifecycleOwner +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.StateFlow interface Camera { - val surfaceRequests: StateFlow + val surfaceRequests: StateFlow suspend fun bind( lifecycleOwner: LifecycleOwner, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 6b8f1681..965a660e 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -11,10 +11,10 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException import androidx.camera.core.Preview -import androidx.camera.core.SurfaceRequest import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat +import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,8 +26,8 @@ class CaptureCamera( private var cameraControl: CameraControl? = null private var cameraInfo: CameraInfo? = null - private val _surfaceRequests = MutableStateFlow(null) - override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() + private val _surfaceRequests = MutableStateFlow(null) + override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() private val imageCapture: ImageCapture = ImageCapture @@ -41,7 +41,7 @@ class CaptureCamera( private val preview: Preview = Preview.Builder().build().apply { setSurfaceProvider { request -> - _surfaceRequests.value = request + _surfaceRequests.value = CameraPreview(request) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index cdeae137..8df4330d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -6,10 +6,6 @@ import com.twix.ui.base.Intent sealed interface TaskCertificationIntent : Intent { data object TakePicture : TaskCertificationIntent - data class BindCamera( - val lifecycleOwner: LifecycleOwner, - ) : TaskCertificationIntent - data class ToggleCamera( val lifecycleOwner: LifecycleOwner, ) : TaskCertificationIntent From 34442dd5514c3dff27f8f03164286c33aa6f3afd Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:03:37 +0900 Subject: [PATCH 36/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EC=B4=AC=EC=98=81=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt --- .../java/com/yapp/twix/main/MainActivity.kt | 5 ++-- .../TaskCertificationScreen.kt | 11 ++++++-- .../TaskCertificationViewModel.kt | 28 ++++--------------- .../model/TaskCertificationIntent.kt | 5 +++- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index ff8c6e45..36dc792e 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.navigation.AppNavHost +import com.twix.task_certification.TaskCertificationRoute class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,7 +22,8 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - AppNavHost() + // AppNavHost() + TaskCertificationRoute {} } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 5545afe1..33f82d40 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -51,13 +51,20 @@ fun TaskCertificationRoute( navigateToBack() }, onCaptureClick = { - viewModel.dispatch(TaskCertificationIntent.TakePicture) + camera.takePicture( + onComplete = { + viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) + }, + onFailure = { + // feat/#38-task-certification-gallery branch에서 작업 예정 + }, + ) }, onToggleCameraClick = { viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) }, ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index de0e0238..a5bd171c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -16,7 +16,7 @@ class TaskCertificationViewModel : override suspend fun handleIntent(intent: TaskCertificationIntent) { when (intent) { is TaskCertificationIntent.TakePicture -> { - takePicture() + takePicture(intent.uri) } is TaskCertificationIntent.ToggleCamera -> { @@ -29,16 +29,11 @@ class TaskCertificationViewModel : } } - private fun takePicture() { -// camera.takePicture( -// onComplete = { -// updateCapturedCImage(it) -// unbindCamera() -// }, -// onFailure = { -// onFailureCapture() -// }, -// ) + private fun takePicture(uri: Uri?) { + uri?.let { + reduce { updateCapturedImage(uri) } + reduce { toggleTorch() } + } ?: run { onFailureCapture() } } private fun onFailureCapture() { @@ -47,17 +42,6 @@ class TaskCertificationViewModel : } } - private fun updateCapturedCImage(uri: Uri?) { - uri?.let { - reduce { updateCapturedImage(uri) } - reduce { toggleTorch() } - } ?: run { - viewModelScope.launch { - emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) - } - } - } - private fun toggleCamera(lifecycleOwner: LifecycleOwner) { reduce { toggleLens() } // bindCamera(lifecycleOwner) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index 8df4330d..f874559c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -1,10 +1,13 @@ package com.twix.task_certification.model +import android.net.Uri import androidx.lifecycle.LifecycleOwner import com.twix.ui.base.Intent sealed interface TaskCertificationIntent : Intent { - data object TakePicture : TaskCertificationIntent + data class TakePicture( + val uri: Uri?, + ) : TaskCertificationIntent data class ToggleCamera( val lifecycleOwner: LifecycleOwner, From 5706c1a4f514a5e56f2b2fc419b65206effd9565 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:09:02 +0900 Subject: [PATCH 37/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/yapp/twix/main/MainActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index 36dc792e..ff8c6e45 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.task_certification.TaskCertificationRoute +import com.twix.navigation.AppNavHost class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,8 +22,7 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - // AppNavHost() - TaskCertificationRoute {} + AppNavHost() } } } From ccfe5e01b673e5017a2f82499fe474fc16ae67cb Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:17:06 +0900 Subject: [PATCH 38/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EB=A0=8C=EC=A6=88=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EC=B1=85=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/task_certification/TaskCertificationScreen.kt | 2 +- .../twix/task_certification/TaskCertificationViewModel.kt | 8 +++----- .../task_certification/model/TaskCertificationIntent.kt | 4 +--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 33f82d40..14c47955 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -61,7 +61,7 @@ fun TaskCertificationRoute( ) }, onToggleCameraClick = { - viewModel.dispatch(TaskCertificationIntent.ToggleCamera(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index a5bd171c..d4c1da15 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -1,7 +1,6 @@ package com.twix.task_certification import android.net.Uri -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect @@ -19,8 +18,8 @@ class TaskCertificationViewModel : takePicture(intent.uri) } - is TaskCertificationIntent.ToggleCamera -> { - toggleCamera(intent.lifecycleOwner) + is TaskCertificationIntent.ToggleLens -> { + toggleLens() } is TaskCertificationIntent.ToggleFlash -> { @@ -42,9 +41,8 @@ class TaskCertificationViewModel : } } - private fun toggleCamera(lifecycleOwner: LifecycleOwner) { + private fun toggleLens() { reduce { toggleLens() } - // bindCamera(lifecycleOwner) } private fun toggleTorch() { diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index f874559c..5579bc74 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -9,9 +9,7 @@ sealed interface TaskCertificationIntent : Intent { val uri: Uri?, ) : TaskCertificationIntent - data class ToggleCamera( - val lifecycleOwner: LifecycleOwner, - ) : TaskCertificationIntent + data object ToggleLens : TaskCertificationIntent data object ToggleFlash : TaskCertificationIntent } From d56df8b47a2ed47620f4bb035ba61a0f43dc522e Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:48:47 +0900 Subject: [PATCH 39/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=B4=AC=EC=98=81=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=BD=9C=EB=B0=B1=EC=97=90=EC=84=9C=20suspend?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 20 +++--- .../twix/task_certification/camera/Camera.kt | 6 +- .../camera/CaptureCamera.kt | 64 +++++++++---------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 14c47955..3fbe55fa 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.coroutineScope import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme @@ -26,6 +27,7 @@ import com.twix.task_certification.component.TaskCertificationTopBar import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationUiState +import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @@ -39,6 +41,7 @@ fun TaskCertificationRoute( val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current + val coroutineScope = lifecycleOwner.lifecycle.coroutineScope LaunchedEffect(uiState.lens) { camera.bind(lifecycleOwner, uiState.lens) @@ -51,14 +54,15 @@ fun TaskCertificationRoute( navigateToBack() }, onCaptureClick = { - camera.takePicture( - onComplete = { - viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) - }, - onFailure = { - // feat/#38-task-certification-gallery branch에서 작업 예정 - }, - ) + coroutineScope.launch { + camera + .takePicture() + .onSuccess { + viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) + }.onFailure { + // feat/#38-task-certification-gallery branch에서 작업 예정 + } + } }, onToggleCameraClick = { viewModel.dispatch(TaskCertificationIntent.ToggleLens) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 966d9ac3..1507612f 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -2,7 +2,6 @@ package com.twix.task_certification.camera import android.net.Uri import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageCaptureException import androidx.lifecycle.LifecycleOwner import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus @@ -18,10 +17,7 @@ interface Camera { suspend fun unbind() - fun takePicture( - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) + suspend fun takePicture(): Result fun toggleTorch(torch: TorchStatus) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 965a660e..70caa956 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -19,6 +19,10 @@ import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException class CaptureCamera( private val context: Context, @@ -64,20 +68,17 @@ class CaptureCamera( cameraInfo = camera.cameraInfo } - override fun takePicture( - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) { - val contentValues = contentValues() - val outputOptions = outputFileOptions(contentValues) - - capture( - imageCapture = imageCapture, - outputOptions = outputOptions, - onComplete = onComplete, - onFailure = onFailure, - ) - } + override suspend fun takePicture(): Result = + suspendCancellableCoroutine { continuation -> + val contentValues = contentValues() + val outputOptions = outputFileOptions(contentValues) + + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(context), + capture(continuation), + ) + } private fun contentValues(): ContentValues = ContentValues().apply { @@ -99,26 +100,23 @@ class CaptureCamera( contentValues, ).build() - private fun capture( - imageCapture: ImageCapture, - outputOptions: ImageCapture.OutputFileOptions, - onComplete: (Uri?) -> Unit, - onFailure: (ImageCaptureException) -> Unit, - ) { - imageCapture.takePicture( - outputOptions, - ContextCompat.getMainExecutor(context), - object : ImageCapture.OnImageSavedCallback { - override fun onImageSaved(result: ImageCapture.OutputFileResults) { - onComplete(result.savedUri) + private fun capture(continuation: Continuation>): ImageCapture.OnImageSavedCallback = + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(result: ImageCapture.OutputFileResults) { + val uri = result.savedUri + if (uri != null) { + continuation.resume(Result.success(uri)) + } else { + continuation.resume( + Result.failure(IllegalStateException(URI_NOT_FOUND_EXCEPTION)), + ) } + } - override fun onError(exception: ImageCaptureException) { - onFailure(exception) - } - }, - ) - } + override fun onError(exception: ImageCaptureException) { + continuation.resumeWithException(exception) + } + } override suspend fun unbind() { ProcessCameraProvider.awaitInstance(context).unbindAll() @@ -137,5 +135,7 @@ class CaptureCamera( private const val IMAGE_MIME_TYPE = "image/jpeg" private const val IMAGE_NAME = "task_%d" private const val IMAGE_PATH = "Pictures/TaskCertification" + + private const val URI_NOT_FOUND_EXCEPTION = "촬영한 이미지의 Uri를 찾을 수 없습니다" } } From b2f436e2392ccd57fbf1a44b781a652d01fe0dc1 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:51:32 +0900 Subject: [PATCH 40/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor::=20Lifecyc?= =?UTF-8?q?leOwner=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/camera/CaptureCamera.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 70caa956..c830434c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -14,6 +14,7 @@ import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TorchStatus import kotlinx.coroutines.flow.MutableStateFlow @@ -50,7 +51,7 @@ class CaptureCamera( } override suspend fun bind( - lifecycleOwner: androidx.lifecycle.LifecycleOwner, + lifecycleOwner: LifecycleOwner, lens: CameraSelector, ) { val provider = ProcessCameraProvider.awaitInstance(context) From c6d7b0de37070326943eca4c3546e3738066f664 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Wed, 28 Jan 2026 02:30:25 +0900 Subject: [PATCH 41/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20lifecycleowner=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 3fbe55fa..5d6b62c3 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -68,7 +68,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash) }, ) } From acaeec3238ad818118b03730cafd8f27a0138186 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:03:37 +0900 Subject: [PATCH 42/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EC=B4=AC=EC=98=81=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt # Conflicts: # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt # feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt --- app/src/main/java/com/yapp/twix/main/MainActivity.kt | 5 +++-- .../com/twix/task_certification/TaskCertificationScreen.kt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index ff8c6e45..36dc792e 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.navigation.AppNavHost +import com.twix.task_certification.TaskCertificationRoute class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,7 +22,8 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - AppNavHost() + // AppNavHost() + TaskCertificationRoute {} } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 5d6b62c3..3fbe55fa 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -68,7 +68,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) }, ) } From 0f480fd616054c4fe892e63bab3ef64f4eef883f Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Wed, 28 Jan 2026 02:30:25 +0900 Subject: [PATCH 43/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20lifecycleowner=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 3fbe55fa..5d6b62c3 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -68,7 +68,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash) }, ) } From 0e22c5183e7dbb783410a03b613e0b6a28def56b Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:03:37 +0900 Subject: [PATCH 44/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EC=B4=AC=EC=98=81=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20UI=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt # Conflicts: # feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt # feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 5d6b62c3..3fbe55fa 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -68,7 +68,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) }, ) } From 0450fefe1bdf7bd4d6919ec01c2a4362d2ecafc6 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 11:32:30 +0900 Subject: [PATCH 45/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=20=ED=94=8C=EB=9E=98=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20=EC=B1=85=EC=9E=84=EC=9D=84=20UI=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 6 +++++- .../twix/task_certification/TaskCertificationViewModel.kt | 6 ++++-- .../task_certification/model/TaskCertificationIntent.kt | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 3fbe55fa..2aad2378 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -47,6 +47,10 @@ fun TaskCertificationRoute( camera.bind(lifecycleOwner, uiState.lens) } + LaunchedEffect(uiState.torch) { + camera.toggleTorch(uiState.torch) + } + TaskCertificationScreen( uiState = uiState, cameraPreview = cameraPreview, @@ -68,7 +72,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash(lifecycleOwner)) + viewModel.dispatch(TaskCertificationIntent.ToggleFlash) }, ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index d4c1da15..dd97dba4 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect import com.twix.task_certification.model.TaskCertificationUiState +import com.twix.task_certification.model.TorchStatus import com.twix.ui.base.BaseViewModel import kotlinx.coroutines.launch @@ -31,7 +32,9 @@ class TaskCertificationViewModel : private fun takePicture(uri: Uri?) { uri?.let { reduce { updateCapturedImage(uri) } - reduce { toggleTorch() } + if (uiState.value.torch == TorchStatus.On) { + reduce { toggleTorch() } + } } ?: run { onFailureCapture() } } @@ -47,6 +50,5 @@ class TaskCertificationViewModel : private fun toggleTorch() { reduce { toggleTorch() } - // camera.toggleTorch(uiState.value.torch) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index 5579bc74..cc714833 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -1,7 +1,6 @@ package com.twix.task_certification.model import android.net.Uri -import androidx.lifecycle.LifecycleOwner import com.twix.ui.base.Intent sealed interface TaskCertificationIntent : Intent { From a0255ea241baf2702a9ab19732ae8e05190538bb Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 12:02:19 +0900 Subject: [PATCH 46/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=B4=AC=EC=98=81=EC=8B=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=BD=9C=EB=B0=B1=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/camera/CaptureCamera.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index c830434c..365bffe0 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.Continuation import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException class CaptureCamera( private val context: Context, @@ -115,7 +114,7 @@ class CaptureCamera( } override fun onError(exception: ImageCaptureException) { - continuation.resumeWithException(exception) + continuation.resume(Result.failure(exception)) } } From e862959b1244056917819150fe88724277595ed8 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 12:04:44 +0900 Subject: [PATCH 47/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/yapp/twix/main/MainActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index 36dc792e..ff8c6e45 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.task_certification.TaskCertificationRoute +import com.twix.navigation.AppNavHost class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,8 +22,7 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - // AppNavHost() - TaskCertificationRoute {} + AppNavHost() } } } From c8b73b687064e943c3ed05211bec38929f1179db Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 13:06:39 +0900 Subject: [PATCH 48/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=B9=B4=EB=A9=94?= =?UTF-8?q?=EB=9D=BC=20=EA=B6=8C=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 2aad2378..3990bb5c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -1,5 +1,9 @@ package com.twix.task_certification +import android.Manifest +import android.content.pm.PackageManager +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -8,11 +12,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.coroutineScope @@ -42,29 +50,54 @@ fun TaskCertificationRoute( val lifecycleOwner = LocalLifecycleOwner.current val coroutineScope = lifecycleOwner.lifecycle.coroutineScope + val context = androidx.compose.ui.platform.LocalContext.current - LaunchedEffect(uiState.lens) { - camera.bind(lifecycleOwner, uiState.lens) + var hasPermission by remember { + mutableStateOf( + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_GRANTED, + ) + } + + val permissionLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission(), + ) { granted -> + hasPermission = granted + } + + LaunchedEffect(Unit) { + if (!hasPermission) { + permissionLauncher.launch(Manifest.permission.CAMERA) + } } - LaunchedEffect(uiState.torch) { - camera.toggleTorch(uiState.torch) + LaunchedEffect(uiState.lens, hasPermission) { + if (hasPermission) { + camera.bind(lifecycleOwner, uiState.lens) + } + } + + LaunchedEffect(uiState.torch, hasPermission) { + if (hasPermission) { + camera.toggleTorch(uiState.torch) + } } TaskCertificationScreen( uiState = uiState, cameraPreview = cameraPreview, - onClickClose = { - navigateToBack() - }, + onClickClose = navigateToBack, onCaptureClick = { + if (!hasPermission) return@TaskCertificationScreen + coroutineScope.launch { camera .takePicture() .onSuccess { viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) - }.onFailure { - // feat/#38-task-certification-gallery branch에서 작업 예정 } } }, From 3f55676807459e9e7856a7af5da307cb080d9b58 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:51:54 +0900 Subject: [PATCH 49/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=86=A0=EC=B9=98=20=ED=86=A0=EA=B8=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=ED=95=A8=EC=88=98,=20=EB=B3=80=EC=88=98=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_certification/TaskCertificationScreen.kt | 2 +- .../task_certification/TaskCertificationViewModel.kt | 12 ++++-------- .../model/TaskCertificationIntent.kt | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 3990bb5c..6f055c56 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -105,7 +105,7 @@ fun TaskCertificationRoute( viewModel.dispatch(TaskCertificationIntent.ToggleLens) }, onClickFlash = { - viewModel.dispatch(TaskCertificationIntent.ToggleFlash) + viewModel.dispatch(TaskCertificationIntent.ToggleTorch) }, ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index dd97dba4..23306f30 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import com.twix.task_certification.model.TaskCertificationIntent import com.twix.task_certification.model.TaskCertificationSideEffect import com.twix.task_certification.model.TaskCertificationUiState -import com.twix.task_certification.model.TorchStatus import com.twix.ui.base.BaseViewModel import kotlinx.coroutines.launch @@ -23,18 +22,15 @@ class TaskCertificationViewModel : toggleLens() } - is TaskCertificationIntent.ToggleFlash -> { - toggleTorch() + is TaskCertificationIntent.ToggleTorch -> { + reduceTorch() } } } private fun takePicture(uri: Uri?) { uri?.let { - reduce { updateCapturedImage(uri) } - if (uiState.value.torch == TorchStatus.On) { - reduce { toggleTorch() } - } + reduce { updatePicture(uri) } } ?: run { onFailureCapture() } } @@ -48,7 +44,7 @@ class TaskCertificationViewModel : reduce { toggleLens() } } - private fun toggleTorch() { + private fun reduceTorch() { reduce { toggleTorch() } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index cc714833..8aca0940 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -10,5 +10,5 @@ sealed interface TaskCertificationIntent : Intent { data object ToggleLens : TaskCertificationIntent - data object ToggleFlash : TaskCertificationIntent + data object ToggleTorch : TaskCertificationIntent } From 4f5b5fc7a91ec2482b3b5b2f9de883f30d9d15c2 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:53:25 +0900 Subject: [PATCH 50/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=B4=AC=EC=98=81=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=EC=B9=98=20off=20=ED=95=A8=EC=88=98=EB=AA=85=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_certification/model/TaskCertificationUiState.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index cfb0e58d..b105a66d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -27,5 +27,9 @@ data class TaskCertificationUiState( return copy(torch = newFlashMode) } - fun updateCapturedImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) + fun updatePicture(uri: Uri): TaskCertificationUiState = + copy( + capture = CaptureStatus.Captured(uri), + torch = TorchStatus.Off, + ) } From f7654f4d1d0100eff28914e3c527691e33a0db26 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:54:52 +0900 Subject: [PATCH 51/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=BA=A1=EC=B3=90=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20sideEffect=20=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationViewModel.kt | 2 +- .../task_certification/model/TaskCertificationSideEffect.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 23306f30..0e860930 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -36,7 +36,7 @@ class TaskCertificationViewModel : private fun onFailureCapture() { viewModelScope.launch { - emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) + emitSideEffect(TaskCertificationSideEffect.ShowImageCaptureFailToast) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt index e9188a6c..28a2de3c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt @@ -3,5 +3,5 @@ package com.twix.task_certification.model import com.twix.ui.base.SideEffect sealed interface TaskCertificationSideEffect : SideEffect { - data object ImageCaptureFailException : TaskCertificationSideEffect + data object ShowImageCaptureFailToast : TaskCertificationSideEffect } From 0658bd39624dda2ba34a59cbcb1828f7a067346a Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:56:37 +0900 Subject: [PATCH 52/58] =?UTF-8?q?=E2=9C=A8=20Feat:=20=ED=9B=84=EB=A9=B4=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=EC=B9=98=20Off=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_certification/model/TaskCertificationUiState.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index b105a66d..3effb53d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -19,7 +19,10 @@ data class TaskCertificationUiState( } else { CameraSelector.DEFAULT_BACK_CAMERA } - return copy(lens = newLens) + return copy( + lens = newLens, + torch = TorchStatus.Off, + ) } fun toggleTorch(): TaskCertificationUiState { From 6d72767d56b480b4bda70dd7cdeaa54bcc51f720 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:58:38 +0900 Subject: [PATCH 53/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20lifecycl?= =?UTF-8?q?e=EC=9D=98=20coroutineScope=EC=97=90=EC=84=9C=20rememberCorouti?= =?UTF-8?q?neScope=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 6f055c56..e84be098 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,7 +50,7 @@ fun TaskCertificationRoute( val cameraPreview by camera.surfaceRequests.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current - val coroutineScope = lifecycleOwner.lifecycle.coroutineScope + val coroutineScope = rememberCoroutineScope() val context = androidx.compose.ui.platform.LocalContext.current var hasPermission by remember { From 170a413b57984ccd4c9d3c0395502b39e298aac0 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 00:59:41 +0900 Subject: [PATCH 54/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=9E=8C=EB=8B=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index e84be098..c86611e4 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -127,7 +127,7 @@ private fun TaskCertificationScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { TaskCertificationTopBar( - onClickClose = { onClickClose() }, + onClickClose = onClickClose, ) Spacer(modifier = Modifier.height(24.26.dp)) From d908bbf9e1b247768af6190f5a4404fb10c3bbac Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 01:20:01 +0900 Subject: [PATCH 55/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B9=B4=EB=A9=94=EB=9D=BC=EB=A5=BC=20=EC=83=9D=EB=AA=85?= =?UTF-8?q?=EC=A3=BC=EA=B8=B0=EC=97=90=20=EB=A7=9E=EA=B2=8C=20unbind=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_certification/TaskCertificationScreen.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index c86611e4..a34de136 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -75,9 +76,17 @@ fun TaskCertificationRoute( } } - LaunchedEffect(uiState.lens, hasPermission) { + DisposableEffect(lifecycleOwner, uiState.lens, hasPermission) { if (hasPermission) { - camera.bind(lifecycleOwner, uiState.lens) + coroutineScope.launch { + camera.bind(lifecycleOwner, uiState.lens) + } + } + + onDispose { + coroutineScope.launch { + camera.unbind() + } } } From 342d9894707048aeaa53269ca54efd1f6fcb3506 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 01:23:17 +0900 Subject: [PATCH 56/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20viewMode?= =?UTF-8?q?l=EC=97=90=20reduce=20=ED=95=A8=EC=88=98=EB=93=A4=EC=9D=98=20pr?= =?UTF-8?q?efix=EC=97=90=20reduce=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/task_certification/TaskCertificationViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 0e860930..34c3981e 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -15,11 +15,11 @@ class TaskCertificationViewModel : override suspend fun handleIntent(intent: TaskCertificationIntent) { when (intent) { is TaskCertificationIntent.TakePicture -> { - takePicture(intent.uri) + reducePicture(intent.uri) } is TaskCertificationIntent.ToggleLens -> { - toggleLens() + reduceLens() } is TaskCertificationIntent.ToggleTorch -> { @@ -28,7 +28,7 @@ class TaskCertificationViewModel : } } - private fun takePicture(uri: Uri?) { + private fun reducePicture(uri: Uri?) { uri?.let { reduce { updatePicture(uri) } } ?: run { onFailureCapture() } @@ -40,7 +40,7 @@ class TaskCertificationViewModel : } } - private fun toggleLens() { + private fun reduceLens() { reduce { toggleLens() } } From 6f6c4b512cba6c2e3acf456d2c3a34b309ad77be Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 20:30:06 +0900 Subject: [PATCH 57/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=A0=84=EB=A9=B4=20=EC=B9=B4=EB=A9=94=EB=9D=BC=EC=9D=BC=20?= =?UTF-8?q?=EB=95=8C=20=ED=86=A0=EC=B9=98=20=EB=B2=84=ED=8A=BC=20=EC=88=A8?= =?UTF-8?q?=EA=B9=80=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 2 +- .../com/twix/task_certification/component/CameraPreviewBox.kt | 4 +++- .../twix/task_certification/model/TaskCertificationUiState.kt | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index a34de136..a39e2ffb 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.coroutineScope import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme @@ -150,6 +149,7 @@ private fun TaskCertificationScreen( Spacer(modifier = Modifier.height(40.dp)) CameraPreviewBox( + showTorch = uiState.showTorch, capture = uiState.capture, previewRequest = cameraPreview, torch = uiState.torch, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt index 96597674..79486dc7 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt @@ -28,6 +28,7 @@ import com.twix.ui.extension.noRippleClickable @Composable fun CameraPreviewBox( + showTorch: Boolean, capture: CaptureStatus, previewRequest: CameraPreview?, torch: TorchStatus, @@ -48,7 +49,7 @@ fun CameraPreviewBox( ) { CameraSurface(capture, previewRequest) - if (capture == CaptureStatus.NotCaptured) { + if (showTorch) { TorchIcon(torch, onClickFlash) } } @@ -107,6 +108,7 @@ fun CameraPreviewBoxNotCapturedPreview() { TwixTheme { CameraPreviewBox( capture = CaptureStatus.NotCaptured, + showTorch = true, torch = TorchStatus.Off, previewRequest = null, onClickFlash = {}, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index 3effb53d..a16f5a18 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -12,6 +12,9 @@ data class TaskCertificationUiState( val lens: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, val preview: CameraPreview? = null, ) : State { + val showTorch: Boolean + get() = capture is CaptureStatus.NotCaptured && lens == CameraSelector.DEFAULT_BACK_CAMERA + fun toggleLens(): TaskCertificationUiState { val newLens = if (lens == CameraSelector.DEFAULT_BACK_CAMERA) { From af7471788ae5be70192930cd19946b9262f5fe00 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 20:39:41 +0900 Subject: [PATCH 58/58] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20camera?= =?UTF-8?q?=20unbinding=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 4 +--- .../main/java/com/twix/task_certification/camera/Camera.kt | 2 +- .../com/twix/task_certification/camera/CaptureCamera.kt | 7 +++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index a39e2ffb..3dd4d8d7 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -83,9 +83,7 @@ fun TaskCertificationRoute( } onDispose { - coroutineScope.launch { - camera.unbind() - } + camera.unbind() } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt index 1507612f..055975dc 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/Camera.kt @@ -15,7 +15,7 @@ interface Camera { lens: CameraSelector, ) - suspend fun unbind() + fun unbind() suspend fun takePicture(): Result diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt index 365bffe0..9ac13295 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/camera/CaptureCamera.kt @@ -29,6 +29,7 @@ class CaptureCamera( ) : Camera { private var cameraControl: CameraControl? = null private var cameraInfo: CameraInfo? = null + private var cameraProvider: ProcessCameraProvider? = null private val _surfaceRequests = MutableStateFlow(null) override val surfaceRequests: StateFlow = _surfaceRequests.asStateFlow() @@ -54,6 +55,8 @@ class CaptureCamera( lens: CameraSelector, ) { val provider = ProcessCameraProvider.awaitInstance(context) + cameraProvider = provider + provider.unbindAll() val camera = @@ -118,8 +121,8 @@ class CaptureCamera( } } - override suspend fun unbind() { - ProcessCameraProvider.awaitInstance(context).unbindAll() + override fun unbind() { + cameraProvider?.unbindAll() } override fun toggleTorch(torch: TorchStatus) {