From 2121ab037eb9ea505e0dcb0959162e19ff820488 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Mon, 14 Jul 2025 22:36:56 +0900 Subject: [PATCH 01/15] =?UTF-8?q?feat:=20open=20ai=EC=9D=98=20realtime=20a?= =?UTF-8?q?pi=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20client?= =?UTF-8?q?=5Fsecret=20value=EB=A5=BC=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EA=B3=A0,=20=ED=8C=8C=EC=8B=B1=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../com/saegil/android/navigation/NavGraph.kt | 25 +++ .../com/saegil/android/navigation/Screen.kt | 4 +- .../main/res/drawable/img_btn_end_call.png | Bin 0 -> 23965 bytes .../src/main/res/drawable/img_gildong.png | Bin 0 -> 18482 bytes .../src/main/res/drawable/img_saerom.png | Bin 0 -> 14910 bytes .../java/com/saegil/data/di/NetworkModule.kt | 1 + .../data/model/GetRealTimeApiTokenReponse.kt | 49 +++++ .../saegil/data/remote/AssistantService.kt | 5 + .../data/remote/AssistantServiceImpl.kt | 47 ++++- .../java/com/saegil/data/remote/HttpRoutes.kt | 2 + .../repository/AssistantRepositoryImpl.kt | 4 + .../domain/repository/AssistantRepository.kt | 2 + .../domain/usecase/GetRealTimeTokenUsecase.kt | 21 +++ presentation/ai_conversation/.gitignore | 1 + presentation/ai_conversation/build.gradle.kts | 74 ++++++++ .../ai_conversation/proguard-rules.pro | 21 +++ .../ExampleInstrumentedTest.kt | 24 +++ .../saegil/ai_conversation/SaegilCharacter.kt | 26 +++ .../aiconversation/AiConversationScreen.kt | 99 ++++++++++ .../aiconversation/AiConversationViewModel.kt | 24 +++ .../AiConversationEndScreen.kt | 20 +++ .../AiConversationEndViewModel.kt | 24 +++ .../AiConversationListScreen.kt | 116 ++++++++++++ .../AiConversationListViewModel.kt | 38 ++++ .../components/CallButton.kt | 54 ++++++ .../components/CharacterCard.kt | 72 ++++++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 ++++ .../main/res/drawable/img_btn_end_call.png | Bin 0 -> 23965 bytes .../src/main/res/drawable/img_gildong.png | Bin 0 -> 18482 bytes .../src/main/res/drawable/img_saerom.png | Bin 0 -> 14910 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../src/main/res/values-night/themes.xml | 16 ++ .../src/main/res/values/colors.xml | 10 ++ .../src/main/res/values/strings.xml | 3 + .../src/main/res/values/themes.xml | 16 ++ .../saegil/ai_conversation/ExampleUnitTest.kt | 17 ++ presentation/learning/build.gradle.kts | 8 + .../learning/learning/LearningScreen.kt | 63 ++++++- settings.gradle.kts | 1 + 52 files changed, 1097 insertions(+), 3 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/img_btn_end_call.png create mode 100644 core/designsystem/src/main/res/drawable/img_gildong.png create mode 100644 core/designsystem/src/main/res/drawable/img_saerom.png create mode 100644 data/src/main/java/com/saegil/data/model/GetRealTimeApiTokenReponse.kt create mode 100644 domain/src/main/java/com/saegil/domain/usecase/GetRealTimeTokenUsecase.kt create mode 100644 presentation/ai_conversation/.gitignore create mode 100644 presentation/ai_conversation/build.gradle.kts create mode 100644 presentation/ai_conversation/proguard-rules.pro create mode 100644 presentation/ai_conversation/src/androidTest/java/com/saegil/ai_conversation/ExampleInstrumentedTest.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndScreen.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndViewModel.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CallButton.kt create mode 100644 presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt create mode 100644 presentation/ai_conversation/src/main/res/drawable/ic_launcher_background.xml create mode 100644 presentation/ai_conversation/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 presentation/ai_conversation/src/main/res/drawable/img_btn_end_call.png create mode 100644 presentation/ai_conversation/src/main/res/drawable/img_gildong.png create mode 100644 presentation/ai_conversation/src/main/res/drawable/img_saerom.png create mode 100644 presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 presentation/ai_conversation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 presentation/ai_conversation/src/main/res/values-night/themes.xml create mode 100644 presentation/ai_conversation/src/main/res/values/colors.xml create mode 100644 presentation/ai_conversation/src/main/res/values/strings.xml create mode 100644 presentation/ai_conversation/src/main/res/values/themes.xml create mode 100644 presentation/ai_conversation/src/test/java/com/saegil/ai_conversation/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 85cfe743..49ce842f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -92,6 +92,7 @@ dependencies { implementation(project(":data")) implementation(project(":core:designsystem")) + implementation(project(":presentation:ai_conversation")) implementation(project(":presentation:news")) implementation(project(":presentation:notice")) implementation(project(":presentation:learning")) diff --git a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt index 7575c5d3..08074db1 100644 --- a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt +++ b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt @@ -1,5 +1,6 @@ package com.saegil.android.navigation +import android.util.Log import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -18,6 +19,10 @@ import com.saegil.notice.notice.NoticeScreen import com.saegil.onboarding.OnboardingScreen import com.saegil.splash.SplashScreen import androidx.core.net.toUri +import com.saegil.ai_conversation.SaegilCharacter +import com.saegil.ai_conversation.aiconversation.AiConversationScreen +import com.saegil.ai_conversation.aiconversationlist.AiConversationListScreen +import com.saegil.learning.learning.components.CharacterEmotion import com.saegil.news.news.NewsScreen import com.saegil.news.newsquiz.NewsQuizScreen @@ -27,6 +32,26 @@ fun NavGraph(navController: NavHostController, modifier: Modifier) { val context = LocalContext.current NavHost(navController = navController, startDestination = Screen.Splash.route) { + + composable(Screen.AiConversation.route) { + AiConversationListScreen( + modifier = modifier, + onCharacterClick = { character -> + navController.navigate("${Screen.AiConversation.route}/$character") + Log.d("character", character) + } + ) + } + composable( + route = "${Screen.AiConversation.route}/{characterString}", + arguments = listOf(navArgument("characterString") { type = NavType.StringType }) + ) { backStackEntry -> + val characterString = backStackEntry.arguments?.getString("characterString") + val character = runCatching { SaegilCharacter.valueOf(characterString ?: "") }.getOrNull() + + AiConversationScreen(character = character) + } + composable(Screen.Learning.route) { LearningListScreen( modifier = modifier, diff --git a/app/src/main/java/com/saegil/android/navigation/Screen.kt b/app/src/main/java/com/saegil/android/navigation/Screen.kt index d75da454..199f52c0 100644 --- a/app/src/main/java/com/saegil/android/navigation/Screen.kt +++ b/app/src/main/java/com/saegil/android/navigation/Screen.kt @@ -4,6 +4,8 @@ import androidx.annotation.DrawableRes import com.saegil.android.R sealed class Screen(val route: String, @DrawableRes val icon: Int?, val label: String) { + object AiConversation : Screen("ai_conversation", R.drawable.ic_pencil, "AI 대화") + object Learning : Screen("learning", R.drawable.ic_pencil, "학습") object Announcement : Screen("announcement", R.drawable.ic_announcement, "공지사항") object Map : Screen("map", R.drawable.ic_location, "지도") @@ -16,6 +18,6 @@ sealed class Screen(val route: String, @DrawableRes val icon: Int?, val label: S data object Quiz : Screen("quiz", null, "퀴즈") companion object { - val items = listOf(Learning, Announcement, News, MyPage) + val items = listOf(AiConversation,Learning, Announcement, News, MyPage) } } \ No newline at end of file diff --git a/core/designsystem/src/main/res/drawable/img_btn_end_call.png b/core/designsystem/src/main/res/drawable/img_btn_end_call.png new file mode 100644 index 0000000000000000000000000000000000000000..c06f7c6946b3a1b5ac20e165edfc3f8fcba580cb GIT binary patch literal 23965 zcmXt9cQl;c*VTI&y)&al7etLRMjJIsFlwUr7MojW zB6|BCzqP(UEDP5>_qq4nbN1PL-$Z>q4RR7@5-cn%a!t6ZAr=-k>i&y}0QgO0htv@8 zjo2G*=8J_Tq<#N|{YfF~3-C*9UqcNTR>cVGHt++Uld`Td7FKl{>9q|$7M7K`rmFJO zAng5b#6kaUd^?L!qM63h1Vy>xswSDB_?SzLEp1QsOP>l7;$=^`W|w{OB2Jonnr^_c*QOgY&x@^r>cQMgR%*2>{+v-Tes&l ze$O!BuorPi2>0Wkf{oTixQ*=RR}{hHU4re4?fVT2|GK>>oA^ zwXY*OJv6HJiIb9auQw+?P*qlLUf8O@8^(q~8ByGp`umykN0>?m58tu*s{UVbtUu04 zb#fmOZ_fKZeMIZNcHS0z98D)^G8=}_doM35vt9HW#@cfir>|=D?2?v7x-Ao4y69+glU;jq;hnG=pTii-_swnfiVf z7M7Xq=980?-D15wbDjdckBLf5N}^c;A|$GPcVVxI5@E^^CS3g3B399W{J_=ejIqc5 zqe(=&J3E(W2a7BWw_<-I`TkQTs`}4kPgLv;Iogp;RRhoIjr>=zJUKbJ@mpyxYiWn? z|IJ6?5=znFBVQ$PH9#KeSyCo0G|g(QEEy${YVy;9GDxEBvE3eUPA~qOstm7Rx8t~) z<+oLP$9eUA{aS3qmQSKtxBR-oYn3WR9rDP_#MD&uYW;x{@hb6fSLZjY?%b#nPr8H2sbsybb%W@d2^LTmkL5pKKFNw{@6zB7TZPX7b&5qNon+9Tl^?y`RRxeVBeHF+mCFz& zlkXL|xsAM(Ooo!)<3*0$Sjs-7M>>+v^qo^sr!Z_tdAAu;i{~R@|M@zX-muGmOk4-q zKgjX9_B&ze9i!0-p{MdZ%9qlW&A)S7!f!6)!t@Lvsr?}^slx=Z56r{o-p^6??rnj>1uxz)1HZ=6@qO&;bC z)~ocP1CXHhzffUvs?=jJa}8yd`WuK@!nM`R;PM}S!~+KFpw(y|?X1|$%*<0pAzB1) z_)(!^+7b0QH7&^Y2zMn3&s8wbZAVT;kn6LzEsp6-j{xs1rLr=VeY-n+kNaa)x-CqI z7A61S^hi_0;YW%8I0=cc+aVHwUW;9EzU%^Yv6zG{PpY z8ncKgzQ#+XxmVYU4SED!p_EwnMjkv1m~{HLzi?*GK%}$`%7_%Sy{`0FXb?tWUCY_~ z+H{!nmOM5{=GM$3rsJW^k6c$A&ZQyQO#UP$Y<40I5r%wtlhE6Hf@*4_fBN((Ba+yb zN9K2~6|+Yrra`4+lW9=*d}dgg8W&TOLQy|6cWF6J>vHG!*T-ZolhSV8 z5{<{2E*s4|)U0w1?Jag~a;MkprADviL&gWJ@u(rY#rCkPGSqXGHEyUX&u=+o_DO&LsufC@Y-igED0La3+Ge%%DSfQU%LG;){X zU73|u?MNrL#OkvLzKM4p%*@Q~PxbZx;P!|#aZ4i_fn{@qVMc|1zBweuD9@M>z!*&8 zSKl5wyYrd!8&aiRk@v=|Q}kT^+df_}?$#Fl=9h=o#A&K;@LWvtX~XXOt5wA|Qw$CD zu3k2AgpqQ`{E=HLSR9}<>sS7ZP0Jnrr;AWp_npiUuDu7(+x#tm?1niDovUFoJ}^^c z-(y!dH|_NwF`eh+@hEwf*mWD?y1T984|gw(3s6Cx!b(QToEeUMU>Ed<-c_9Sp|705 zu++uB|TO1tv>U{?_IxTdPtDLjyYlVr0>5IE$w(jOPR5m&sZIJ zxTF+%wiswy{;cD6vZbYk$S_ca9_?y`R9LfVLD5acDIb-R z@wBC!b%beAB7M@8y^R*Ufd*%_a}bCJp;g3k@Md1+lXo)J0@dNF$)G4CNouvQ7D;}j zpO^r+PL61s71)>Xr`FcDTxC~L0sEEs-NlpYjrne+rN9{M`w`|V)6{P37-AaUm)_S$ z{cnXW8qn0`RF%1TIOA#$NlQsP6X+=sYziU&5L!pzB46c(=@NrNYl{}Y?LdCA3f-S%0l?@MT8Thu1zqUN_Quf5sMLbw>Mpy7LDox>&yu- zRj&hhC;d?#;qo9wbSo3y_uifJ1e<%+D?_N_&A2s7f&qY|#b@!)F<@(;K zZVBD|NVZsd_+tYj1nx#WMj5V-QQEuZjY}MLOujam~apldvd5aX^|xL@IJ9Dqt+MtY^F&hH!Ii{BGGiI*EeN6044W`Qh%B+{y3vQrNz5nh?C|!crlwAl$`5{q zc)Cmm64B;V9qxIs7ksoJO4A(In2nlqjvgIB*bC;#WHQEwD+bkap6OVj*dZ=m)af2N z3S_*xx%QRBTU)!oD_LHENRTRrA~`N<@LK}}BIx+5aj7SjSRz#Qd19GW>$FKFi>L|f zBi%2q4Wb`DMg}2+I{z1@2~bsf5z8Pi6&01Dl9GWwH8eG`z}uEf^WC22dgnx$zvfUs zok$_=^&`sSZR(8We_KD-NR5q+M~8F73M1_7>>k>piCAcSM1;sI+?=@&%TaA%xKHJE zjGhSM{g}&&1=*&%b=OEQ1+57rMm@dA-0exAOsh6o2VV$)8%Q54RMEi9aq2nmE@wJK zOsJGCg7!4ZktG`EDm|wUF1EUB3N(+vv8)u?zps9A+@1HYjnTIk7Z*d_)OdM@tVT`$ z?qr|+ZTf*${S*X(r4k8I`8Zj<=%q7#`8?Fw*tj>u_$eZgKDFmlW~K3Nuw9h%w5pZW zq7N-44DJ@i^J3oRv%=N->nB-|s`l2hq3I-WZ{Si}aD%mKrRYUgUM2%hL0I`p!_G|Q zax!poh+J^-@$prD{PAe!6%q1CBKi>nJak)VWds&5P4h(Fp!h(D>OU;}1Y`m-2rW46n`e1L9#b_K zXxx|D5{s(9IuIf*_~5^mB0c?;htr43E$aDF3uvO@L9=V(dQqgzOs2<^ZZmdZBo5tf z#O1_?KHaxc9va#QLJSPVzCSq!o0s8h?0WJ7$kxGVkW}b&GAI$w;(lM@@2k%tKAzf3jtTj zyQ2l@*B1WFkcl16k_`twmPDY(iDqrNO2AM{mOy6-Xad#d4Jqzvz!QjkP ztLJ?K>0A-Z-$Ku30d!GzBh2iqxaAbmapU7)Y1)%}r%}i$QBCk3-H5*ru4N zvX@vsI5J8#8|p}9WQqt$-l)olyZmo(QGR~@OAIUOy`syf6BnwY?*R0*8u8%r=%T>HBxB1?eC4ku#XhGMe{)5B$@ zc8f;Op(f5)d;EJeDQVaDD!9dI^t}OE#h$5vybZ0|?LIQ}U5*q_*_SHq86)s!)C835 z*DO>?H5iQTka1&Nd#h?B(^O8+vb*-HgVrB) zi^LcR5TglX1>YwHPIRt6md*9O`5lXT2`x*D>t_$$+iVFuWLMi<`uq3qY?iQv?ak#0 zsi2^shxuWdS#9oDG6^9G!8IWeGNI&%{642Cf(v{`LJz;43IF?HXiATP3=EA2!HWqd zB=!@=FW8-bTH6BeMu}qi-I8q`vi<9e(PxvX>FK_@n^rXJxeq#5ubS%QmFvxW9~|lw z^%-VID=OGuvZfmKH1?Zj05|vT?CiKt^kOiWSpfRzTQUYZI2ca!>b$T~me&_T+%Mc{ zq~I=awy(1A#<}K2wPU%X^IKYK#8Y0Ke?-|tHvh)7n7aYPo zDv`9rDOON$pB`EKIdLwa1K2!y0;|wb`QxEGdM};)a=nLx`e?3blLt$|Oa}Hj0mLzp z18+}wf|dBdK#g(l>3!n_^coQi^z`bxySs(J){^!JAjKfJ3~Va=}3b8j*Ss3!)sNklpyt z$dU6C1C!anP&c2Y8D%eZVs7!|QeA4dZZba358N~W#E1dQPb5qG?|aB<9K%QyU5!mo zJeCjYqe{Y#Zo~?^OFhg`1RVY(J32c00dPW4MPy9k>lFV6VLiS6yVLlG9e3@@nR1q9 zeZ+9N(n%u`Fq}PdFfM>6UXmjgG|u_rwbkwAZe;{1lUWcP{0&%AIn}o`tO-Hja%rn! zdLsewrFIK(jQfNh5kv!so^9!0-Z9P^)&35Uat8ePzWOjiP=+58u7!{G&mar=x7C&8 z3lL`bCFB?1P20tBE6NJf6z3blG)$&7ALQA)sXSC+`ZVGt` zhySF3YaF6_j5z+w!JEwQcVu6K^Z3^9fV>0oa2(E+%uZpEKG@)93-$OB_|A@NhyN@2 z_6t(<1|MO?ibECzcz+Qfn+GKPLTc36RhpETK7Bfy-|{~D>pV(?0krvArSeLY>3_3Z z^xK{G_=o9Du2hDyQKrNp@on{4qGfl8ia&of^3t*epF5C{kjx$?fnah2ol&@-BhFfv zg_X@KIY^bER--;)H7%BEh7d6P@m0ph6J?-;t9uS4gs+l5JLeSw+1rydJ_JY4gOEhy zAyoQLjQvBjgh1x^8x(xc7qCb#`WZzqkS4a))WO(_I6pO(;>S)C0<~0}Po(YdcfpRR zI%_1dPX(ku(`EF9s3`D@obt^&Lr>1UxC#-ZXrl9cLDu*8yT5;WC=W}%eoYEETv8}@ z`Ic=JVD@xWrBY}Hzi47xi(x_f+Qu{wEUCJ&l{NgpypFf1RK$A&Z z#kop|z5~HpK*aCv?pp+~%{m?(GWmPJ$T!%KRXc20IHL&WSGdSp6B zKu|~c&Gdbo@^1n;?eSgmvulh6pTOQi;~~Uk;8(Nneod`-4e+VW0CUXA$-Wn?Mg(yw zXluL9RSdhFt!{LUd*0C?y}ieqoW}{SQY;2KC>;|~=~w`Kz-(+*jc2SwJ&1930M@6D zjXK0$2jUIGk zGh34f{QU`lWTxb*K@0t?zOQt*r%Vpv0pfY8P6)6%ZN+$%HwqY&`fDg#>3B$D)An0Y z5%>7m7-(;?b;cZ0LfRFc1IbIzoErAS`CDrVbWE}Kc>QdFzi6AQBY3D2V~L6ZfJ` zMijl>!ch%k&}qDyf15rfg8g%(^{>qG{gdHINlC>eEI&T|Q=d%PFfDFuY^qwM9{`AL z0dhAbppMxoJIMV)FiOSA5S`v14vcB>?w5TZhk7%p{BF#bRv{cA)!~_L-?lQ?zh($ z)noX|^8%b6HPAnpD#h6rAS7yVo2qYtrs;j*c3bXvvRxI-@0Uj?2JGQ)Gk43M4v#$$ zyvKE^j}K%lXbdea-L1^Rb&*LY1-XTAKJDZ4TI@?hXZ7CBAJajwXl{FO%e!A*p zXJZZ#0^yDOn?sUF@{o1&WG+wGi169uEbtX2*uN@y}Ip^AG zE7Tak46*if_ifby0E!@d0_c(j0A(NvCViq$H#~F>3Z8 zk=pO$6;NnuShGi3^%oI9rzQU2oi_~V7diK4Dmh$X-sZEpGVoGcFmgy6?@k^l0opeh&Hr#l*(OlfTW-s0M=n)>!%@J=-7p*qoVq&GO6%1> zt37tUnwn89~0)DS>h$Zt(DSCeozh#hN$QmJXIr692 z?5nLjsIa-BtcG zTjyeA$)}&03TEG zLRhmFshNN8u9-8fbsUEIy%!P62%el_s;ynhp)6et$s^v`-4FS*dRgpi|3QJk`~B$1 zWlX<$Pk#cpJEelXg5PHBr{`IHcel%?N$K)^ASfWsyEW`q5d` z)#F?HfM;JvMU@FKTdLQ8hN@}l#$9L`7%Zge@ql&}tf~MH3;UK519wM@a=%JWe4;G| z+U=&sW}3)4ijPHjQ(a$4=-M7#cJKMiI*=fj2YT8+2eyU@2r#5dtd{)ri%JiO5^JQ^ z(a=5vDgPUOE6w8L(3{4EVwZ9-<_Wungx2G7iC1}kJDFxS6BYvqAOypm<)q){D?;Jq zN!;Z1SJk@FFMiJr$6UwOMOq&2##6A#6#!+8*F@v#8kqVj^`7|#kP1`#zj1AYcW;F# zGIXHcwyM@eML->qj7O=0L86LwoOds$MEDt*h-~>m{}G#-*+7bT97o?PU;sF=7zMpN zSZrnaiJ-#aJSb;m7UV?T_Hf*SFh2GSS#P0?Qc_alVcD#BCVix%taajWN8Ie04}XG+ z_;*RO2Gz0|eSgZRAV)W{pDBinyptv6N{5#mp~lC@p`i{@F=2FXEg%WB)EL#K%Jp78 z@I@+-n)l2T-F1o2xdR)Sn20<6UM?18k5@#R>kBiv1D_RbqM2rn58)X2$}fU3FzY;m z3b)u#PcF4ttg+nJ#-^ifR@5Ol26BNfB1O5Rt12- z{2hbbu(5+sTc!c!3Y6vz&|M!%i#e8Xh*{r3bDWR})%Kv^2{r-Yll`KU!!=9OdCDJN`>Bj@?Kg;FTd*|&eQc~qnWaW~O2UBySbjP98-Fr|H07$l zQ7(q3PU|mDwS0UZ2`TQ8*_=mH)z}hVUhsX>ki|P4mRXkb&_zN`8aH;kwV6bO7DJe3 z8D##tUik=2QkF5-h$uz(X;FtF)8FCNgJ-M)t3-m-h*6ES{&3&$C`*TzX`3dcP(us`JxP z7E{LKpzf|&0vEyi(Pd?2mMV+`=oeReV)nN6k1$d$%w7PRb&guc`Rnl68#Tfy!X0?? zA1uQT$x;dzP6K-2AOTURTOi)}>ra1qQ=$fvS)Jgg(JM{Ic1ALAr@#c4TGg5n6_<#V0!NHt$+7g(YMwbb&ux9f>tO%m_B|b|n7L73vh5&89 zwE!pelZ`28ixQWD*ex~&rvRUmv4}lUbyiSr0nyyXFC&sg7_-Lh(5Xw(IK6jx?3>*S zpS9hpuxfoZ0_S+-tbcvqqr0=BQ)IkO<(K0eo?VuW62oHwQc{1Y!jW9W(BhS!9ZT=MHFw4)P|}oatJn!kBgLLZ9k%@0~N9pqn$Y z!I#_z21SEU2ch6aN#j?I{%0{;@2uW#uV@GF2PF{>GVcvjkbt(wzXz#8-ETffJw*pu zbbR5Ic}GerhKj`gN7n0Tmh$;Wl091kR=P;}D-ByVH2@5d;q}Jea|EyRO6A6d2sm+~ zo#ji%B*^PHseZ&fgJ#svRuHYd;m_&%w>MV{HwvbCD=_jwX<1~kE-gqZiw?9bU~n8~ zPXJG@D~E64fag-(%2gQL9ml`r=QmZlXl?XLxhk)8!^CXV)Z(V_8JQ}+6f8F26dX|o zRt=$EVPT@`;IJ3Ow5{{u=G=u_KTBs&Ii>0|VF;z$^EHc?BM^F`K@j&ea1ff4$SaIg z#Zv)*jSw3PI6y?)-JI=<75yE_mDDCBdFy0@4OAwqI5CtTuF>7X3=`}HQ*ogay|WX> zfkGhtuabm7c3`+i>@-9pp*lDQc~dn)65RDo zOUgyB^?hWjf5q02X+sE9-m>2$<)$`fQqFMr)t9|oz^h@)m*^akYt@xQUhCS5ig+w9 z$NMohpH_0n`8mfS*u>-kI(CtdGuq*)Di5!+pFFG{hD!W2OpCjZ0_0_vsA&ztPW(;u zPhM7jTc5tUzJVHTA;M)&S-UbeibbIo8cD?3F)Ns^M2 zhnK#EdT1q|?6waTteoSTM}a2BLSD%;Dql5^9?lGXtx9hwsbOhYO>F9r_UR}4 z^Ebb)xvn~wmroVeQdDU!^9G*I*i*!i2_=Tb&W|fl75%+LU`Oq+dQ#JE%i$zxNWiKe z!Vt_CXeN;?qo?SnCWLsBMUh4C05q;5zbB_^ztG@e18f<&++R+lV#MuBdmA$!mx1LypzYvG*9uu{iI{^9MV>9QE0GFt1hziyDi3$s=si=_`Naw{w z2n&$lsncSZj1U4erR$DP*j@u~jyL76AS-@kq; z9<3_qZxc7anOBrUIm0rJC;C3#Q;5TS(}hb6spz+*k5+OAvGH)skE`ON8{d_ZX|k~z zvk=BbT~I9JQkrk_AP2B0@RbiL&jnMID#BO&qmhs8%SOE(`SxlpLICeq(-Tbt#yA~aU2^qbeQAZ6iM5K+j$)wmHg7Dd)4o1+5_r;} zTZFVQDyxevNU^nAiwReg2MFm}Wxth~C@?8dI<03ZteQ0#DeHDMM}(BI2MObgvRfSP{qqu zJDLiqb)xlBmSjx&i+f7UwazCSk?^J5gPlZ!Of}TzO|f@>muDM@av0qLFp_4383cdF zmxr_1G`OGPZoi5sdVhC4diN`T*V_glJ3B?RwYAF;&#fncOrAUSuKz7RZyUWtwV`hN zMXN#7sQew*>(9p9LnGS4TQT(jNdyuD9l_?J3`5*|;>nqDkNV$Br$s@HLGXm{1FuWT zHHCwJA5yr4)xCe&mLned-tZwQUO^Ni+`u5cUjCY%SJ9eiJwMV}!`j|9G3O!bJ;mRF zl-^UG@&}@|{}^#K`JIdS=#k|Yk@y%66~REE*Tc!KCh)ELZ3k9v^7WLCw>^dP@olET z8{?}^TpRaxDvu82*pszh84Hh%_efE&90Qw~xp92ad-;FCR|oGE05bM>Nww}|rH zq^)_jZ`I%~ZdfNOW zoUDk;{}47}H&bns z@gq5RU$-r$_IfyuYZz3mLz@n0A9MTnCcr^g=w5J3L2+_&jYOvO;(A6aPREa7CGn!y z(06<96JG2R#7t|N)+korbJ7JfF(F-Lme059neGEGrT1#tMj% zVP~iFCg-8k-m21O-__R+QcY6eAceMmGHB@B?G7bMcva_L4|d+y#va5&FXs&_`(M=_ zkJPw%Gc36WI`tj|qof&K)&W&AoCVu2QZx2Bz?%;n2Ca5$WyR5P&oxXC-}~a^FSiO8 z>|4>OI+|ZMMl`=pj9%+P<=ur6{AB0OFRChJs`ZY;PeYlN38sl*%F4=sVMSb!et0z6oSB`n5 zM5(%HnuzV^J;09ON5*uL1aEyzsM!5k*ndbiD1ZB}B(c-s$cFhX!ONG(Z_XbB4T661 z(5C1VT3^pqii&sbR^~q6_**>sJz-aOKC%;1i~8{Tm9p*Pv-=MFgR)>?VzBB46^&p~ zHLwt3#3dRwQDlINl%NpDfjv>A%rjAR>x1`l*qJa0TZ|i*YC?(Nn1ccNfL*fd1G`ur zK`eI4l^(54XU(&8Vr_?K9hl-)^|_JK?Zi2fTXoxd+f%JtFNIiqSWWFnl2#S=?g?*X z=gO>JU|aj^kgwess5pKc*V=58kDa7=)mSjYLT>k4MkoWe=H)ebZloBYiFHOVckC%i zR^&todL4mD_5Jyg0s8@-R6I^;R5#QdS5ZYksD^z&d@Ss=$!TiKiH!bBz0O8~@2X6A zu{bSxe$OS8rWX+U)P3yK(f}F?5f&Ea*BOazFogZ3tn8KSF76Yz0MU%6Q1mig)Ts7* z#*0?)6Z!v<$V{pat=S}9C-MBBB{uQ{+-^@zRn^0mkdvJpXXvaU0X>pC)W+j0GGaZL z^j;f$2P;c4z4`8omHwwuO&->$wC%>q2?jBjkelbO3{rn`5)cJgI>xD~spRTOAtAa{ zh)Vn{J+E^ClAd=kmro#p2p;{08ZaU!xPOHcL1gx$$mRrYs|_e8O- z2`=akg>?xrA_{BaS=fFnXjH88B@9q6vl8X<-0xXg1wsl|#8D?M($=wspVKL#G7Y#srae-~DT)zs9G zVd%$)S(%w}$Os{`8t(V<{!N#ZpNw7F2JrjfDw}<_1`uHKOaT4~rq@O*Q7@X#Ql1$* zIPfHq#Z04>pp9I4E1V|{Z>9m*0K0&^r-n2y-Oq{n`k&R}12HN=R%U46zBYx0{MN({ z3}(Q6}Ua;*^Kg9LC)8>2c-ayxNuJR%akZT!KRJrib@y7)l!9Jt&^OaZ)&T}H`>TcVf{ z`TS)$!DzZoX`vgXG*loR`1tmmTf#q?T`B)t^+$kJW62V&R8_IvT3=s3JfFSwhmWDc z`NXYJ4w-t#D4H4qdiqI%B^d-0XKAXeXu>3^AtL~hy*ELFoL!z#L`YB|=VsOESzP%S zuwR`3LBaX=*yq6cTeB*7-pG6}iS+{q;8`y<0Tv07Bt`VxCSZ9;B^=M3ZmIZB>eCVI z4FJu|yGUfcSFjRvfC!8KJ0vlB%^DpwB#4YDKEN25m^eO^Cpi!d$^sQY+ggPymwB)x_u55C48hu%$W5GsC|3TCg)XT(#Yk{{aE}^Cz=M{Tvavo_~2z z>@RusIuX3IG+6+p2e&*R! zj_D~xg?*~$fPSWEnbmk(o&l_pK==n*s`Tf3v(=&<7r)>BSSOU91AG3>$)tdsAwo0^Z}k@jrGmS zZy;{t_PEoyxL>6G9Mgz+10?S+pMjJ(#U$ZaCLMOg+~J>ymbWE(uL6BIs%dF@n!8V) z>MG4f4Z7Z(Z>I7TDzOd=yGgq*X#dr`y!iWgDz}sRjcIO+g^>}BZwgI0e)RkJl8s&D z3HE$uU+Gp6tc>deBmXx*waY#2IS52kP#E@^{2?rY;O0OEao)A^g>$HP&5p{TKKOSbV&=Oq zlCWXQ-`LWURVDhh;^%K_F@sxmrZ2h}3#~#{`^=lXS1AGO&QTa};)*7|R8+hV*#4BQ z*x9pXZ}yq zRJSq}$l`4El0J8&82Dfjc71s~&(6U1<2<&jvqPsepa^V(85uuKCoDfQctEi;U@qz} z{qOTY0B;^((27c;aMQ3Fpfj~R@)n}ExEH7END&-BQNOeUkv+|OpvS5@q3U6QLkW66 z@iVnGq43y)`LO;d<_U1GuLf{(LD|!@pM_ssXJlk_Fai13gTSBT!^Myjdp&&u(SYJi z1D+%YfRtXj?i?i12^JIwuc1W@()Ajl@_Ur8uqeg{K%}yVZO8Q9*7uKQtCo4M@)L~4 zHQsE^6K5oJx69)N8=;65yLuP;U;Zn16I5uySD zNra<>WQ@-N=OF#WEMaF#>kQAgA1@JB0^s?1ybh$Jed0x2FhpmUCl+N=128uI?RPha zm4}Ce23%` zdaL`J%TX1AoYX46GlTr~9}&$MBj20uwBuOOUFo;{ire_USuQ$~Ir)~wv&kmm`fn{@ zF-G;&01l)(FTe{jRV9U{RnE3zIUMV3_O*%wB+Ap5O_e%HTi=C*l1~p?0DldR0sQJN zQIh)JXk@?fJ+tx$i%$&@#V?4e6!m0QV&!=Vkrn>z%Mz0=P zkCs*pwPa5Qba+JoUt>@WE!b&^exPB?u?jF79<1D%N0cZnQdJh=z8Lwh@Ax9`vF{AI zl^Nj2}uU{Jqv!C8Q+E#W`8Nz3<)W;H3 zU3!(@-hN9EycX^XEHdR>gU3SNeXbk5>1-y_m^|rlRoyEDTA{B|zQ_bO27E(?Ywsd$ z=)XG(Rb^b_uY`ZqRneS@ZGXB+{Tu)zDh1xi6TplqakjtUWMvYX>zKDtCJ3xrnES6L zF_NE93qD#$J9~A3+6wAz$MIOwQ*)75_gDL;$ z;@Z7G+u>ZH6V0Tx%uQTRQRaHi0lgJBwUdcJtD{;3is*g1ToYTF zA#lr}J=qrswhOG93dO6r(WL}`@|;2|Vl-|yHYr|A!6R>gkk0mZ_$SM&TQr5^(;@S9 zk_xw)3Giqtn@m4GZ0ji#g2DA-n)-p807o2E@{e{&vJ%j8?!*1SRS#S9|yws5!o>fgl<`VP%cfX(-3Q*uvs*@4fdpI=TzEt+k-n>TMM)F=FRAqS0nW9R0gW z(YsuyaaT+8iW2KuT4kvd?%?n#?_)dA$q%$1wqHq{e&>u`XZH8^KTBbI96(=2iHsL2 zYz^2?veBn)*(+G4GYnX5VU~2Ib$s_$$H$7F$tH^19)xx(>B&67B#SPII9k^O2c8wX zq7<;uF$8|yC7~q3LE~0a9<`&_*qi7JBJwFVe0yhI7${LxI2n;1+GJG9eO;sBr?uwQ zu-ir(d7!l8f*njT>@;_O5|k!)Uit3(XU+;4gjzM*cD(|Te|Y6>!(U-akD~%=fLfXH zJ$$+I<#UBorW}NOAsy?Y?0Dqyp>^2tee_^(%`w!$*cGDovQ zkY~*jk^7ziyGvzq<`Uax1j5ewd->kbX$D}3yOU>kC%$Ecme@X&c*79x7(&VhGt=Cf zWELkNQH*Fz8;|+f49PH<%$vVqjURKn@?4hjaj*$``DfoNqSeIG%(P~T7{Uz%6OzRh zgG(u|U#Ux74Y<;)U0OjIz+U7V6nwLh(i2)~(Q>LRhj&~*`Zj89?_tx<=9JlAtKAh z2Bdes+G7G$mv0G4gcx>C*3*#B`&z$#*jL4TnvA9Na_LO~Le zC_HV|(#dqeduP~Jpu$`6_wNFrwup(1A}(LHVYOylJ+VrjfSlQW!k_BVeSRcQ!uQdg zFyWa~H9>$=J9Wh-Kg^kY^_s2vh^2hu@FUAgu%cWRSkCc*d#nD2wn+23EQ0L@B;)mU zzsj=|kPjbM&>s(p#nBxEH8oCN4y^yTXm1~fk28tmV?;(y;vPvb^+fSvJwX-j2&}0l z%|KHNCQ0!9F4u0@MsUqA3Mn|_ zW!s#NxRq9|^z{(z=pBHcO{Y{mU$MT33G2H5e^lrBFEP8mU(YA}tswuFc-?x6cU@nr zlILLn8BplGf#s*QzV2+%6>kYOQ8B%$NGLd^oS{N4!(=3~j993gHu zVNm`FrAhZE zomjPp7Rram1Wx;j5TVh@+8aGbe+XCS4OsJ#tWdBgV-=XxL>xUp_x z?J_5CLHKI7ds*VsI}tu8@I`6X#h=g0A^%V9^`wG$7g6@j*eUc;2UBY{Pzf;$uA;Dn z{QUep5N>Z8T`R<)Q(RkGh|kj5lWOG54GauI-rV#w($~}lYEdlF zS#)N9x4ejPB*7pSYx_Pqcq`##s7sxUvq$!xd}gl^MdFJx(s zKwZoWEEnSiaa!LRUUysVk<>be5(ibDtoM0_G2#+#n>%B$SnMui{@Ovfg(1HVD0DbDNfS@*ye_oJY6%RT_GdEEufBSIt z|EL$SY;L;t1x(fSzHg>O*6>v~=MS#!WuFjl-t#z1lX1Eapm)#MfCrmp2yd;bvh}rQ z+$jWakhh0=>I52YCbqm@sH7semIF)1JU`#f( zgfP=m@NxxZwnI06a|$sfTnU-64^kW=Q=?%FE^lq=StM6D{crJ*ymXhAB?m>#g)&gx z<3aU!@i9IGkool%YHp5t02aRl*eYX&+eN_nqOTA#rY;-5$>0=J1FsN<{Hzk_+b?yv zq-iK}R(*J{T`@UnrYVT&NGU{Sep}eNEPJPqQQ3iqv^?x3z+~N73MbAg@EXTZ^_01W zFIxK`5l8BLXmzPzwBow8sSOvK)N$SVa&C_Zlorq~WQDypt+vs$avYwO;5U_s4h)Q}3q2E2FH6oTs_nKF! z|Gw)K2X1#Uh6Q4qndrVbf@t(pB5=?qR z@cCf*`qx$pmHQ0pRyLDtNrR~Jk zGk)B6C<1L`+>dVqs#*S3>$HN)JMRGc4Js_U{8@h^%D~%2S#+g1%#WuhzpiegLy?E; zg#K(EKH#$v%a71Tr*iF${d5Z96$g?t zME8aRt&;5BIX$2?6%h88yYqbR4w*iq#!X2KYd~8xlxrE>`QM{^@-A0Z(ol@3VTaA& z&)=iRaAJ)12Ii69sJ=?TDZX=L%&E-TQ+iufGCnwv*$&Try*)lK=7Hx>;N+~ZyBZr|YLz*IV8TPMl8MU9TeK$VqOGQ75jo9`CJ zgCaiU2{Vtq_A$Cb(Oy~lKQTj_%w)InZi}3Go|-+^Lrj3g6RVgUWi0}Cv1uhT;iBoM zOXG?hKZaVPSzU{OO`um5#dtqV#n}EzoI*#}q+O)MP!m9+R%*n)P!<&Rz_~jDIC*-G zZ8Dqzf4@8m(lKr8wBcWQ7qGp8Cfg2UO6LYk&LBp92W4rs(&wS39Zl z>v;{T0wE#+Cj1c`0&H=O4S+Un%yjc%APgNH_;T}OGtSEhR&#?Z_=T&3g98pJ zAEDM5aHJ`r&1zP~zou><>dm!BpSf-k*&WLuwH*Rs|B|2IS{Rzwp|M-DoaXc!u}5Tj zr1@R4yb{&;;Y%$bC9hvzwdX9qLLWUu=;_DTa+WIaa9lr+crLJB*Xj$Nfnzv7-I8!< z3g5ff=(rxEwW3P8W{V9``iTZj{1{$JpUgK(9RrmT6h-SCM?{=F0Wor|$rz|Rx%-0t zL`ZV<$+k!nDPKg}&O6bKBcW@Pz@5=W>7o_Je%D`KQAy8jdEe)$zmb~!wra%-&YHWz z&oMAAKCwO8qbu~(I*2%J0^ET8rB@R&eeHj2&p_QCiDrjKlWv#R`-|Oo)$}9DH$*u2 zAez|bz1f<+t!DWkV6tow5mDXB0S}^ZUIQ*88Uti&s^=|tPLTEWyiWM{VVqwgfs8Tz z2ge#mD7V@Vb^qfVBs_lrB2c47HA}al)au#~*_jME&&4i0pg4EnJGZB;WTEsQ=krIh zXP}tPG(HAim9s;02?q^g+fl{P*w_wt`-hEL#dI?7MjW^8*wOCwbVAT|bfmXMEjuAk zCM ztdGpa2+6+IFCZ_8{PY>~So$&f%+gQB{-4Q;#h`iq#>7qCJnNmSWiE`CrkfJN0|1smNF!mXh}tEPf?qi?C|zxR^) z+aBWCh)#Ro-p&HjoCB_JSWeH5mqhx~< zP*^fBOR%E85%o|gp#^Z~m1^K7+L_s2rbW!|IxHS~K@Ype3Ey}?8{0`=Ny;bs{SG-T zip?STfEnXNd^>;FKo<-Ns%9yK_*-t{V9b@MMZ%#H@ariUBipz|3vQqr+mnPjfKCbw z=W2OyQ9vm-?jDLE^4OH4tc)E#;uabdDC%XXHbiq6OieWR8?6``6!9bJtR8j7h-Xhp;cC=LmzEQ>iPqqa73{~3v>gwC0VYOZ>?+);f0?AZc z@wksYq3+*+bM2e6g)?DK1%Y~0w7xsKbF4U>K@017A6X&lzdO~&FF5*0jV8U-L*BlY zvQLmimGjg~z#bz#5Jx*jdMto84Ia)MZH#=7e+{_Oyd0c-8cZ{ClW}xDBF3Ma>(P zksEc#)8Im>e(C>^@O8hEYTYWLD84iSd_xQCPz;D`p1`H>v^UjDaQ5Lb%NoSqQ<$ag z+swah-vmcZn4>HJt~x!DE|r9LWV2A;F2oKX{!AMd|HBQADm$wrbLt_#lE9egD$ahH znBekQw5{QM@eU$VT>Z+I=kh=#!We)hm;HE=e0T>84F{sT|@RGH8Y=S zR|Jpp*#yt3?$+id>PJ$#x~5Tf$uwKpyV%MwCvwcs`rdxmV11{0;2HRgF~H?78b9Nv zD?z0RW)6ZU&7R5OOR%c&?uP@EMiKMI7&ZtU5&kDNCaNy4;;9;?8n^Iycze=y=FZyD zd5Dpr^52sm$LejqhVsXv>|D#BbJ1m4efG6D7YR>*(nC&J@^Zdfj>2i4g}ii_))ov)^*!pV}`PDp(#H5nbX3j@F_ zjs?sxroyv5#x07eM-&v@mm(rpFN&^2-D=K0kilTiH%fyw`#`77-j&a!;s-d4v@1#@xFD*`8mmkg(+zanS@@$^R0e5ILV6j5ARnpNjO+Uex(LH^#Bmw-T znoTu$55^`Bej>3-Cu#cZp~4#En5Bv-U9Pn~($SYfUoe=l`tk1NIj||!9JH>bFzkP$ zXh5sO`XP)L-J!^!e7;*Qyj@P7#?kdgM{NJUS-*0>C=5FsbHn}7m(&vp5d?J^UC@tE z>IL=-p3k=bz6i6)(^Cpu%B70l^2IBR>!iy9kk4IncJG>IMWQv}K|4a!A~hEMYovay z7~!k9q|TT~&#D>~&GI6hGrg2o*mH}2K)8TK(COOenc&$_>h-BIBs;4$ zdrtObqmxdG2pj%Qlq6f4cBIcE>Daq+KeKvR8S1|5gK1G*Ici1c@9W3BdQ1c{$J5wu zBE?E`JEgw1b5_`=_>HlW&S^K|zbp@Ze_%w@BlwQ2*SrCZP@v1nj&(6*^ z5o>^zG#pJ70QYqP3)EGDdB53GXfxd2o(0B*e`}COuSJbDFtDkkS`c<)i-T&UkvsQ!X1v?X!}!1}HCt~foD zcLowA1|q(VOZ#c^l44!4t!max?iwzMgwx4VF-U#0hJ`p7M&&;vljxGwB*}<}$rqV{gU1#OJaC1JUqZILB>UTvC&kUn|Yc>$ZB?hjEQjvK8GY9&|;b`cu!FIj@U~2;<9`g2ctm|5$LJ5tkS(pOnWY1r zAzy%f6oV3=nnznUfrcTqJPa)!(2U2M{}D=xOhZ?@_$1`MDiJHlFreR0Go2@Ug`km# zr4+MIlV@r#071s@r#8Yq9Op0%07M0c+7N0N(lD38Ny0bh0+smN+Slv+DeDy zyv2TUaIM^e#LgMr6twlRGTX*M+40m9h8St71Tjfx{(7g*cuviPwZAbN&iP7q z5UvCMr?&L=K(k)fN^M zU{1G|4{c{uL*I*;f0KKqxct=Eyhe^silIW4YXidak>P_7j*Q-i?W$ z`t20WOG~CVS9|kw!YmsoJQmQj&2h+ydR%Z=q1qFm0K|^w@(3DMk!Px9laViS z{FEE_xm2Z3frOo2P?}3Fk0&g|Y>#cJ>+c?OJ8+B9Jjia}33)pU=~2%fSZU1<8Tmx& zhAT3;F|mZ6F{qTTSSac#1{=<1F0Gof`d)OW;0O5QW^R#D-99{5x<`N2n)}H0rJ1>-`ZDa{4B6l3Q78*eR}nWGt20z11qgf9+TB zM3K@{z-qz_88W0;XN31n=eEv&4V-tCl&tS@EyxWc2)uZ{7I|loZrN0f_DIE2pp;Jc zCgNz^#>Gvt!T!ve{HGa}JN*#$5P{td(j~v~fH5^*xu2EURvw?;+jaR2D+y=silUTp z9+wJuX9B&rv;eGncXGVht>KP8g_*rOHl+SEG*?l53pKf3ykfli+sov-fE{qYbp~qi zdlc=G=F-2Xzs{TtX1V2*5XuM4FJu2cGQK1c*T!@(ND!CP8~Qmwn3K-0OPPC;n z?|J<(%d}}+U$U=`&T5aHJ4-GUsKAw!mPVxJ#!l$!N25f%ULSIh4+={qt6KIUT@}ZSc1Rq;mhWPR( zWH~Xa3+i7>20fnliqnQf*S9$yTmot8B-$De)r(av!~PG; CV=|op literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable/img_gildong.png b/core/designsystem/src/main/res/drawable/img_gildong.png new file mode 100644 index 0000000000000000000000000000000000000000..3e1c84035732bc82beedba8d459ba633c53710e2 GIT binary patch literal 18482 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable/img_saerom.png b/core/designsystem/src/main/res/drawable/img_saerom.png new file mode 100644 index 0000000000000000000000000000000000000000..329d7956d0b39a2c97daf9fb0eac7a8c7bf44a40 GIT binary patch literal 14910 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

H@#-)_3>HYpj;oL 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 literal 0 HcmV?d00001 diff --git a/data/src/main/java/com/saegil/data/di/NetworkModule.kt b/data/src/main/java/com/saegil/data/di/NetworkModule.kt index d9cffd28..8d42ec95 100644 --- a/data/src/main/java/com/saegil/data/di/NetworkModule.kt +++ b/data/src/main/java/com/saegil/data/di/NetworkModule.kt @@ -9,6 +9,7 @@ import com.saegil.data.remote.FeedServiceImpl import com.saegil.data.remote.HttpRoutes.ASSISTANT import com.saegil.data.remote.HttpRoutes.NEWS import com.saegil.data.remote.HttpRoutes.NEWS_INTERESTS +import com.saegil.data.remote.HttpRoutes.GET_REALTIME_TOKEN import com.saegil.data.remote.HttpRoutes.OAUTH_LOGOUT import com.saegil.data.remote.HttpRoutes.OAUTH_VALIDATE_TOKEN import com.saegil.data.remote.HttpRoutes.OAUTH_WITHDRAWAL diff --git a/data/src/main/java/com/saegil/data/model/GetRealTimeApiTokenReponse.kt b/data/src/main/java/com/saegil/data/model/GetRealTimeApiTokenReponse.kt new file mode 100644 index 00000000..e3b049a1 --- /dev/null +++ b/data/src/main/java/com/saegil/data/model/GetRealTimeApiTokenReponse.kt @@ -0,0 +1,49 @@ +package com.saegil.data.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RealtimeResponse( + val id: String, + val `object`: String, + val model: String, + val modalities: List, + val instructions: String, + val voice: String, + val input_audio_format: String, + val output_audio_format: String, + val input_audio_transcription: InputAudioTranscription, + val turn_detection: String, // null 허용 + val tools: List = emptyList(), + val tool_choice: String, + val temperature: Double, + val speed: Double, + val tracing: String, // "auto" + val max_response_output_tokens: Int, + val client_secret: ClientSecret +){ + fun toDomain(): String { + return client_secret.value + } +} + +@Serializable +data class InputAudioTranscription( + val model: String +) + +//@Serializable +//data class TurnDetection( +// // 현재 예시에선 null이므로 생략 가능. 나중에 구조 생기면 필드 추가 +//) +// +//@Serializable +//data class Tool( +// // 현재 예시에선 빈 객체 리스트, 추후 구조 생기면 필드 추가 +//) + +@Serializable +data class ClientSecret( + val value: String, + val expires_at: Long +) diff --git a/data/src/main/java/com/saegil/data/remote/AssistantService.kt b/data/src/main/java/com/saegil/data/remote/AssistantService.kt index c0e95199..c6131932 100644 --- a/data/src/main/java/com/saegil/data/remote/AssistantService.kt +++ b/data/src/main/java/com/saegil/data/remote/AssistantService.kt @@ -1,5 +1,6 @@ package com.saegil.data.remote +import com.saegil.data.model.RealtimeResponse import com.saegil.data.model.UploadAudioDto import java.io.File @@ -7,4 +8,8 @@ interface AssistantService { suspend fun getAssistant(file: File, threadId: String?, scenarioId: Int): UploadAudioDto + suspend fun realtimeAssistant() + + suspend fun getRealtimeToken(): String? + } \ No newline at end of file diff --git a/data/src/main/java/com/saegil/data/remote/AssistantServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/AssistantServiceImpl.kt index c4e13876..7a0a6ad1 100644 --- a/data/src/main/java/com/saegil/data/remote/AssistantServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/AssistantServiceImpl.kt @@ -1,13 +1,26 @@ package com.saegil.data.remote +import android.util.Log +import com.saegil.data.model.OrganizationDto +import com.saegil.data.model.RealtimeResponse import com.saegil.data.model.UploadAudioDto import io.ktor.client.HttpClient import io.ktor.client.call.body +import io.ktor.client.request.accept import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.submitFormWithBinaryData +import io.ktor.client.request.get +import io.ktor.client.request.headers import io.ktor.client.request.parameter +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType import io.ktor.http.Headers import io.ktor.http.HttpHeaders +import io.ktor.http.URLBuilder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import java.io.File import javax.inject.Inject @@ -15,7 +28,11 @@ class AssistantServiceImpl @Inject constructor( private val client: HttpClient ) : AssistantService { - override suspend fun getAssistant(file: File, threadId: String?, scenarioId: Int): UploadAudioDto { + override suspend fun getAssistant( + file: File, + threadId: String?, + scenarioId: Int + ): UploadAudioDto { val response = client.submitFormWithBinaryData( url = HttpRoutes.ASSISTANT, formData = formData { @@ -31,4 +48,32 @@ class AssistantServiceImpl @Inject constructor( } return response.body() } + + override suspend fun getRealtimeToken(): String? { + + val urlBuilder = URLBuilder(HttpRoutes.GET_REALTIME_TOKEN) + + val response = client.get(urlBuilder.build()) { + headers { + accept(ContentType.Application.Json) + } + } + val responseBody = response.bodyAsText() + val json = Json.parseToJsonElement(responseBody) + + // JSON 구조에서 client_secret.value 접근 + val value = json + .jsonObject["client_secret"] + ?.jsonObject + ?.get("value") + ?.jsonPrimitive + ?.contentOrNull + + Log.d("value", value.toString()) + return value.toString() + } + + override suspend fun realtimeAssistant() { + TODO("Not yet implemented") + } } diff --git a/data/src/main/java/com/saegil/data/remote/HttpRoutes.kt b/data/src/main/java/com/saegil/data/remote/HttpRoutes.kt index c297bfe3..b916cfe4 100644 --- a/data/src/main/java/com/saegil/data/remote/HttpRoutes.kt +++ b/data/src/main/java/com/saegil/data/remote/HttpRoutes.kt @@ -24,6 +24,8 @@ object HttpRoutes { const val ASSISTANT = "$BASE_URL/api/v2/llm/assistant/upload" // 음성 파일로부터 Assistant 응답 가져오기 + const val GET_REALTIME_TOKEN = "$BASE_URL/api/realtime/token" + const val TTS = "$BASE_URL/api/v1/llm/tts" const val SIMULATION_LOG = "$BASE_URL/api/v1/simulations" diff --git a/data/src/main/java/com/saegil/data/repository/AssistantRepositoryImpl.kt b/data/src/main/java/com/saegil/data/repository/AssistantRepositoryImpl.kt index e8ec59fb..f0cb9613 100644 --- a/data/src/main/java/com/saegil/data/repository/AssistantRepositoryImpl.kt +++ b/data/src/main/java/com/saegil/data/repository/AssistantRepositoryImpl.kt @@ -30,4 +30,8 @@ class AssistantRepositoryImpl( override suspend fun clearThreadId() { threadPreferencesManager.clearThreadId() } + + override suspend fun getRealTimeApiToken() : String?{ + return assistantService.getRealtimeToken() + } } diff --git a/domain/src/main/java/com/saegil/domain/repository/AssistantRepository.kt b/domain/src/main/java/com/saegil/domain/repository/AssistantRepository.kt index 88943e74..988de254 100644 --- a/domain/src/main/java/com/saegil/domain/repository/AssistantRepository.kt +++ b/domain/src/main/java/com/saegil/domain/repository/AssistantRepository.kt @@ -15,4 +15,6 @@ interface AssistantRepository { suspend fun saveThreadId(threadId: String) fun getThreadId(): Flow suspend fun clearThreadId() + + suspend fun getRealTimeApiToken(): String? } diff --git a/domain/src/main/java/com/saegil/domain/usecase/GetRealTimeTokenUsecase.kt b/domain/src/main/java/com/saegil/domain/usecase/GetRealTimeTokenUsecase.kt new file mode 100644 index 00000000..b92ad435 --- /dev/null +++ b/domain/src/main/java/com/saegil/domain/usecase/GetRealTimeTokenUsecase.kt @@ -0,0 +1,21 @@ +package com.saegil.domain.usecase + +import android.util.Log +import com.saegil.domain.model.Recruitment +import com.saegil.domain.repository.AssistantRepository +import com.saegil.domain.repository.MapRepository +import kotlinx.coroutines.flow.Flow +import java.lang.reflect.Constructor +import javax.inject.Inject + +class GetRealTimeTokenUsecase @Inject constructor( + private val assistantRepository: AssistantRepository +) { + suspend operator fun invoke(): String { + val token = assistantRepository.getRealTimeApiToken() + Log.d("token", token.toString()) + return token.toString() + } + +} + diff --git a/presentation/ai_conversation/.gitignore b/presentation/ai_conversation/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/presentation/ai_conversation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/presentation/ai_conversation/build.gradle.kts b/presentation/ai_conversation/build.gradle.kts new file mode 100644 index 00000000..14323a5f --- /dev/null +++ b/presentation/ai_conversation/build.gradle.kts @@ -0,0 +1,74 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.devtools.ksp) +} + +android { + namespace = "com.saegil.ai_conversation" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.runtime.android) + implementation(libs.androidx.ui.tooling.preview.android) + implementation(libs.androidx.foundation.layout.android) + implementation(project(":core:common")) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + debugImplementation(libs.androidx.ui.tooling) + + //hilt + implementation(libs.hilt.android) + implementation(libs.androidx.hilt.navigation.compose) + ksp(libs.hilt.android.compiler) + + //Coil + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) + + implementation(project(":core:designsystem")) + implementation(project(":domain")) + + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") +} \ No newline at end of file diff --git a/presentation/ai_conversation/proguard-rules.pro b/presentation/ai_conversation/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/presentation/ai_conversation/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/presentation/ai_conversation/src/androidTest/java/com/saegil/ai_conversation/ExampleInstrumentedTest.kt b/presentation/ai_conversation/src/androidTest/java/com/saegil/ai_conversation/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..ee704147 --- /dev/null +++ b/presentation/ai_conversation/src/androidTest/java/com/saegil/ai_conversation/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.saegil.ai_conversation + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.saegil.ai_conversation", appContext.packageName) + } +} \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt new file mode 100644 index 00000000..10d6b4fc --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt @@ -0,0 +1,26 @@ +package com.saegil.ai_conversation + +import com.saegil.ai_conversation.R + +enum class SaegilCharacter( + val img: Int, + val nickname: String, + val gender: String, + val personality: String, + val description: String +) { + SAEROM( + R.drawable.img_saerom, + "새롬", + "여", + "ENFP", + "동갑내기 친구" + ), + GILDONG( + R.drawable.img_gildong, + "길동", + "남", + "INTJ", + "연장자" + ) +} \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt new file mode 100644 index 00000000..7b0fcc2e --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt @@ -0,0 +1,99 @@ +package com.saegil.ai_conversation.aiconversation + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +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.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.focusModifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.saegil.ai_conversation.R +import com.saegil.ai_conversation.SaegilCharacter +import com.saegil.designsystem.theme.SaegilAndroidTheme +import com.saegil.designsystem.theme.h1 + +@Composable +fun AiConversationScreen( + character: SaegilCharacter?, + modifier: Modifier = Modifier, + viewModel: AiConversationViewModel = hiltViewModel(), +) { + + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + InternalAiConversationScreen( + state = state, + modifier = modifier + ) + +} + +@Composable +internal fun InternalAiConversationScreen( + state: AiConversationState, + modifier: Modifier +) { + Surface( + modifier = modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) + { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("전화 거는 중...") + Spacer(modifier = Modifier.height(10.dp)) + Text("동갑내기 친구와 \"반말로\" 편안하게 대화해보세요!") + Spacer(modifier = Modifier.height(10.dp)) + + Image( + painterResource(R.drawable.img_saerom), + modifier = Modifier.size(200.dp), + contentDescription = "새롬" + ) + + Text( + "새롬", + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.h1, + modifier = Modifier.padding(top = 10.dp) + ) + Spacer(modifier = Modifier.height(120.dp)) + + Image( + painterResource(R.drawable.img_btn_end_call), + modifier = Modifier.size(96.dp), + contentDescription = "전화 종료" + ) + } + + + } +} + +@Composable +@Preview(name = "AiConversation") +private fun AiConversationScreenPreview() { + SaegilAndroidTheme { + InternalAiConversationScreen( + state = AiConversationState(), + modifier = Modifier + ) + } +} + diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt new file mode 100644 index 00000000..7eb750ca --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt @@ -0,0 +1,24 @@ +package com.saegil.ai_conversation.aiconversation + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@HiltViewModel +class AiConversationViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ViewModel() { + + private val _stateFlow: MutableStateFlow = + MutableStateFlow(AiConversationState()) + + val stateFlow: StateFlow = _stateFlow.asStateFlow() + + +} + +class AiConversationState \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndScreen.kt new file mode 100644 index 00000000..73246ed5 --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndScreen.kt @@ -0,0 +1,20 @@ +package com.saegil.ai_conversation.aiconversationend + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview + +@Composable +fun AiConversationEndScreen( + +) { + // TODO UI Rendering +} + +@Composable +@Preview(name = "AiConversationEnd") +private fun AiConversationEndScreenPreview() { + AiConversationEndScreen( + + ) +} + diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndViewModel.kt new file mode 100644 index 00000000..98d2a348 --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationend/AiConversationEndViewModel.kt @@ -0,0 +1,24 @@ +package com.saegil.ai_conversation.aiconversationend + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@HiltViewModel +class AiConversationEndViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ViewModel() { + + private val _stateFlow: MutableStateFlow = + MutableStateFlow(AiConversationEndState()) + + val stateFlow: StateFlow = _stateFlow.asStateFlow() + + +} + +class AiConversationEndState \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt new file mode 100644 index 00000000..b939732d --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt @@ -0,0 +1,116 @@ +package com.saegil.ai_conversation.aiconversationlist + +import android.util.Log +import androidx.compose.foundation.clickable + + +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.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.saegil.ai_conversation.aiconversationlist.components.CharacterCard +import com.saegil.ai_conversation.SaegilCharacter +import com.saegil.designsystem.component.SaegilTitleText +import com.saegil.designsystem.theme.SaegilAndroidTheme + +@Composable +fun AiConversationListScreen( + modifier: Modifier = Modifier, + onCharacterClick: (String) -> Unit = { character -> }, + viewModel: AiConversationListViewModel = hiltViewModel(), +) { + val learningListState by viewModel.stateFlow.collectAsStateWithLifecycle() + + InternalAiConversationListScreen( + aiConversationListState = learningListState, + modifier = modifier, + onCharacterClick = { character -> + onCharacterClick(character) + viewModel.onRequestToken() + }, + getToken = viewModel::onRequestToken + ) +} + +@Composable +internal fun InternalAiConversationListScreen( + aiConversationListState: AiConversationListState, + modifier: Modifier = Modifier, + onCharacterClick: (String) -> Unit = { character -> }, + getToken: () -> Unit = {}, +// onScenarioClick: (Long, String) -> Unit = { id, name -> }, +) { + Surface( + modifier = modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Column { + SaegilTitleText( + "AI 전화 회화", + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + + Spacer(modifier = Modifier.height(60.dp)) + CharacterCard( + modifier = Modifier, + SaegilCharacter.SAEROM, + onClick = { + Log.d("screen character", SaegilCharacter.SAEROM.name) + onCharacterClick(SaegilCharacter.SAEROM.name) + getToken() + }) + + Spacer(modifier = Modifier.height(10.dp)) + + CharacterCard(modifier = Modifier.clickable { + onCharacterClick(SaegilCharacter.GILDONG.name) + getToken() + }, SaegilCharacter.GILDONG) + + +// when (aiConversationListState) { +// is LearningListUiState.Loading -> { +// // TODO: Show loading state +// } +// is LearningListUiState.Success -> { +// LazyColumn( +// modifier = Modifier.fillMaxSize(), +// contentPadding = PaddingValues(36.dp), +// verticalArrangement = Arrangement.spacedBy(12.dp) +// ) { +// items(learningListState.organizationList) { item -> +// ScenarioItem( +// name = item.name, +// iconImageUrl = item.iconImageUrl, +// onClick = { onScenarioClick(item.id, item.name) } +// ) +// } +// } +// } +// } + } + } +} + +@Composable +@Preview(name = "Learning") +private fun LearningScreenPreview() { + SaegilAndroidTheme { + Surface { + InternalAiConversationListScreen( + aiConversationListState = AiConversationListState() + ) + } + } +} + diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt new file mode 100644 index 00000000..ca79749d --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt @@ -0,0 +1,38 @@ +package com.saegil.ai_conversation.aiconversationlist + +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.saegil.domain.usecase.GetRealTimeTokenUsecase +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +@HiltViewModel +class AiConversationListViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val getRealtimeTokenUseCase: GetRealTimeTokenUsecase + +) : ViewModel() { + + private val _stateFlow: MutableStateFlow = + MutableStateFlow(AiConversationListState()) + + val stateFlow: StateFlow = _stateFlow.asStateFlow() + + + fun onRequestToken() { + Log.d("AiConversationListViewModel", "onRequestToken called") + viewModelScope.launch { + val result = getRealtimeTokenUseCase() + Log.d("result", "${result}") + } + } + +} + +class AiConversationListState \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CallButton.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CallButton.kt new file mode 100644 index 00000000..13a09e5c --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CallButton.kt @@ -0,0 +1,54 @@ +package com.saegil.ai_conversation.aiconversationlist.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.saegil.designsystem.theme.h2 + +@Composable +fun CallButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF31C015), // #31C015 + contentColor = Color.White + ), + shape = RoundedCornerShape( + topStart = 8.dp, + topEnd = 8.dp, + bottomStart = 8.dp, + bottomEnd = 8.dp + ), + modifier = modifier + .fillMaxWidth() + .height(52.dp), + ) { + Text( + text = "전화 걸기", + color = MaterialTheme.colorScheme.background, + style = MaterialTheme.typography.h2, + ) + } + +} + + +@Preview +@Composable +fun CallButtonPreview() { + CallButton(onClick = {}) +} \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt new file mode 100644 index 00000000..33ef946d --- /dev/null +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt @@ -0,0 +1,72 @@ +package com.saegil.ai_conversation.aiconversationlist.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.saegil.ai_conversation.SaegilCharacter +import com.saegil.designsystem.theme.body2 +import com.saegil.designsystem.theme.h2 + +@Composable +fun CharacterCard( + modifier: Modifier=Modifier, + character: SaegilCharacter, + onClick: () -> Unit = {} +) { + + Card(modifier = modifier.clickable { +// onClick() + } + + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Image(painterResource(character.img), contentDescription = "새봄 프로필 사진") + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + ) { + Text(character.nickname, style = MaterialTheme.typography.h2, color = Color.Blue) + Spacer(modifier = Modifier.height(12.dp)) + Text(character.gender, style = MaterialTheme.typography.body2) + Spacer(modifier = Modifier.height(4.dp)) + Text(character.personality, style = MaterialTheme.typography.body2) + Spacer(modifier = Modifier.height(4.dp)) + Text(character.description, style = MaterialTheme.typography.body2) + } + } + + CallButton(onClick = { + onClick() + }, modifier = Modifier.padding(20.dp)) + } +} + + +@Preview +@Composable +fun CharacterCardPreview() { + Column { + CharacterCard(modifier = Modifier, SaegilCharacter.SAEROM) + + CharacterCard(modifier = Modifier, SaegilCharacter.GILDONG) + } +} \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/drawable/ic_launcher_background.xml b/presentation/ai_conversation/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/presentation/ai_conversation/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/ai_conversation/src/main/res/drawable/ic_launcher_foreground.xml b/presentation/ai_conversation/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/presentation/ai_conversation/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/drawable/img_btn_end_call.png b/presentation/ai_conversation/src/main/res/drawable/img_btn_end_call.png new file mode 100644 index 0000000000000000000000000000000000000000..c06f7c6946b3a1b5ac20e165edfc3f8fcba580cb GIT binary patch literal 23965 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 CV=|op literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/drawable/img_gildong.png b/presentation/ai_conversation/src/main/res/drawable/img_gildong.png new file mode 100644 index 0000000000000000000000000000000000000000..3e1c84035732bc82beedba8d459ba633c53710e2 GIT binary patch literal 18482 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 literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/drawable/img_saerom.png b/presentation/ai_conversation/src/main/res/drawable/img_saerom.png new file mode 100644 index 0000000000000000000000000000000000000000..329d7956d0b39a2c97daf9fb0eac7a8c7bf44a40 GIT binary patch literal 14910 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

H@#-)_3>HYpj;oL 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 literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/presentation/ai_conversation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher.webp b/presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/presentation/ai_conversation/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/mipmap-mdpi/ic_launcher.webp b/presentation/ai_conversation/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher.webp b/presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/presentation/ai_conversation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/presentation/ai_conversation/src/main/res/values-night/themes.xml b/presentation/ai_conversation/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..639b2316 --- /dev/null +++ b/presentation/ai_conversation/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/values/colors.xml b/presentation/ai_conversation/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/presentation/ai_conversation/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/values/strings.xml b/presentation/ai_conversation/src/main/res/values/strings.xml new file mode 100644 index 00000000..b70f8d9b --- /dev/null +++ b/presentation/ai_conversation/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Ai Conversation + \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/res/values/themes.xml b/presentation/ai_conversation/src/main/res/values/themes.xml new file mode 100644 index 00000000..8bb42e60 --- /dev/null +++ b/presentation/ai_conversation/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/presentation/ai_conversation/src/test/java/com/saegil/ai_conversation/ExampleUnitTest.kt b/presentation/ai_conversation/src/test/java/com/saegil/ai_conversation/ExampleUnitTest.kt new file mode 100644 index 00000000..b31084f5 --- /dev/null +++ b/presentation/ai_conversation/src/test/java/com/saegil/ai_conversation/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.saegil.ai_conversation + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/presentation/learning/build.gradle.kts b/presentation/learning/build.gradle.kts index b4f5be03..d307c76d 100644 --- a/presentation/learning/build.gradle.kts +++ b/presentation/learning/build.gradle.kts @@ -71,4 +71,12 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.11.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + + + implementation("io.ktor:ktor-client-core:2.3.7") + implementation("io.ktor:ktor-client-cio:2.3.7") // CIO 엔진 + implementation("io.ktor:ktor-client-websockets:2.3.7") + implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7") + + } \ No newline at end of file diff --git a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt index 5bdb77e7..b9375680 100644 --- a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt +++ b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt @@ -51,7 +51,15 @@ import com.saegil.designsystem.theme.h2 import com.saegil.designsystem.theme.h3 import com.saegil.learning.learning.components.CharacterEmotion import kotlinx.coroutines.delay - +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.websocket.* +import io.ktor.client.request.header +import io.ktor.http.HttpMethod +import io.ktor.websocket.* +import kotlinx.coroutines.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* @Composable fun LearningScreen( modifier: Modifier = Modifier, @@ -286,6 +294,59 @@ fun RecordButton( } + +@Serializable +data class StartMessage( + val type: String = "start", + val model: String = "gpt-4o", + val messages: List +) + +@Serializable +data class Message( + val role: String, + val content: String +) + +fun startOpenAIWebSocket(apiKey: String) { + val client = HttpClient(CIO) { + install(WebSockets) + } + + CoroutineScope(Dispatchers.IO).launch { + client.webSocket( + method = HttpMethod.Get, + host = "api.openai.com", + path = "/v1/realtime", + request = { + header("Authorization", "Bearer $apiKey") + header("Content-Type", "application/json") + } + ) { + println("✅ WebSocket 연결 성공") + + // JSON 메시지 생성 + val message = StartMessage( + messages = listOf( + Message("user", "Hello, how are you?") + ) + ) + val json = Json.encodeToString(message) + send(Frame.Text(json)) + + // 수신 루프 + for (frame in incoming) { + when (frame) { + is Frame.Text -> println("📥 수신: ${frame.readText()}") + is Frame.Close -> println("❌ 연결 종료됨") + else -> {} + } + } + } + } +} + + @Composable @Preview(name = "Learning", apiLevel = 33) private fun LearningScreenPreview() { diff --git a/settings.gradle.kts b/settings.gradle.kts index ceb1eda5..f1d058e1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,4 +37,5 @@ include(":presentation:splash") include(":core:ui") include(":presentation:log") include(":core:common") +include(":presentation:ai_conversation") include(":presentation:news") From 7e27ec9a0cc2b2306263d9cebf0635ad43fd0a95 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Mon, 14 Jul 2025 23:39:31 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=EC=86=8C=EC=BC=93=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 + data/build.gradle.kts | 6 +++ .../java/com/saegil/data/di/DataModule.kt | 9 ++++ .../java/com/saegil/data/di/NetworkModule.kt | 20 +++++-- .../com/saegil/data/remote/RealTimeService.kt | 5 ++ .../saegil/data/remote/RealTimeServiceImpl.kt | 51 ++++++++++++++++++ .../data/repository/RealTimeRepositoryImpl.kt | 13 +++++ .../domain/repository/RealTimeRepository.kt | 5 ++ .../usecase/StartRealtimeChatUseCase.kt | 12 +++++ presentation/ai_conversation/build.gradle.kts | 2 + .../aiconversation/AiConversationScreen.kt | 20 +++++-- .../aiconversation/AiConversationViewModel.kt | 28 +++++++++- .../AiConversationListScreen.kt | 5 -- .../AiConversationListViewModel.kt | 8 --- .../learning/learning/LearningScreen.kt | 53 ------------------- 15 files changed, 163 insertions(+), 76 deletions(-) create mode 100644 data/src/main/java/com/saegil/data/remote/RealTimeService.kt create mode 100644 data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt create mode 100644 data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt create mode 100644 domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt create mode 100644 domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 49ce842f..5d483d0e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,6 +24,8 @@ android { versionName = "1.0.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + manifestPlaceholders["OPEN_AI_API_KEY"] = properties.getProperty("openai_api_key") + buildConfigField("String", "OPEN_AI_API_KEY", "\"${properties["openai_api_key"]}\"") manifestPlaceholders["NAVER_CLIENT_ID"] = properties.getProperty("naver_client_id") manifestPlaceholders["NATIVE_APP_KEY"] = properties.getProperty("kakao_native_app_key") buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["kakao_native_app_key"]}\"") diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 5441fa19..71be1a9c 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -105,4 +105,10 @@ dependencies { implementation(libs.androidx.datastore.preferences) implementation(project(":domain"))//클린아키텍처 도메인 의존 + + implementation("io.ktor:ktor-client-websockets:2.3.5") + implementation("io.ktor:ktor-client-content-negotiation:2.3.5") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + + } \ No newline at end of file diff --git a/data/src/main/java/com/saegil/data/di/DataModule.kt b/data/src/main/java/com/saegil/data/di/DataModule.kt index 0a52cab7..ab28a696 100644 --- a/data/src/main/java/com/saegil/data/di/DataModule.kt +++ b/data/src/main/java/com/saegil/data/di/DataModule.kt @@ -11,6 +11,7 @@ import com.saegil.data.remote.MapService import com.saegil.data.remote.NewsService import com.saegil.data.remote.OAuthService import com.saegil.data.remote.QuizService +import com.saegil.data.remote.RealTimeService import com.saegil.data.remote.ScenarioService import com.saegil.data.remote.SimulationLogService import com.saegil.data.remote.TextToSpeechService @@ -21,6 +22,7 @@ import com.saegil.data.repository.MapRepositoryImpl import com.saegil.data.repository.NewsRepositoryImpl import com.saegil.data.repository.OAuthRepositoryImpl import com.saegil.data.repository.QuizRepositoryImpl +import com.saegil.data.repository.RealTimeRepositoryImpl import com.saegil.data.repository.ScenarioRepositoryImpl import com.saegil.data.repository.SimulationLogRepositoryImpl import com.saegil.data.repository.TextToSpeechRepositoryImpl @@ -32,6 +34,7 @@ import com.saegil.domain.repository.MapRepository import com.saegil.domain.repository.NewsRepository import com.saegil.domain.repository.OAuthRepository import com.saegil.domain.repository.QuizRepository +import com.saegil.domain.repository.RealTimeRepository import com.saegil.domain.repository.ScenarioRepository import com.saegil.domain.repository.SimulationLogRepository import com.saegil.domain.repository.TextToSpeechRepository @@ -110,6 +113,12 @@ object DataModule { return UserInfoRepositoryImpl(userInfoService) } + @Provides + @Singleton + fun provideRealTimeRepository(realTimeService: RealTimeService): RealTimeRepository { + return RealTimeRepositoryImpl(realTimeService) + } + @Provides @Singleton fun provideUserPreferenceRepository(interestService: InterestService): UserTopicRepository { diff --git a/data/src/main/java/com/saegil/data/di/NetworkModule.kt b/data/src/main/java/com/saegil/data/di/NetworkModule.kt index 8d42ec95..d15dc7ab 100644 --- a/data/src/main/java/com/saegil/data/di/NetworkModule.kt +++ b/data/src/main/java/com/saegil/data/di/NetworkModule.kt @@ -7,8 +7,6 @@ import com.saegil.data.remote.AssistantServiceImpl import com.saegil.data.remote.FeedService import com.saegil.data.remote.FeedServiceImpl import com.saegil.data.remote.HttpRoutes.ASSISTANT -import com.saegil.data.remote.HttpRoutes.NEWS -import com.saegil.data.remote.HttpRoutes.NEWS_INTERESTS import com.saegil.data.remote.HttpRoutes.GET_REALTIME_TOKEN import com.saegil.data.remote.HttpRoutes.OAUTH_LOGOUT import com.saegil.data.remote.HttpRoutes.OAUTH_VALIDATE_TOKEN @@ -24,6 +22,8 @@ import com.saegil.data.remote.NewsService import com.saegil.data.remote.NewsServiceImpl import com.saegil.data.remote.OAuthService import com.saegil.data.remote.OAuthServiceImpl +import com.saegil.data.remote.RealTimeService +import com.saegil.data.remote.RealTimeServiceImpl import com.saegil.data.remote.QuizService import com.saegil.data.remote.QuizServiceImpl import com.saegil.data.remote.ScenarioService @@ -40,6 +40,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.ktor.client.HttpClient import io.ktor.client.engine.android.Android +import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.DefaultRequest import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.logging.LogLevel @@ -61,7 +62,7 @@ object NetworkModule { fun provideHttpClient( tokenDataSource: TokenDataSource ): HttpClient { - return HttpClient(Android) { + return HttpClient(CIO) { install(Logging) { level = LogLevel.ALL logger = Logger.SIMPLE @@ -83,12 +84,15 @@ object NetworkModule { SIMULATION_LOG, TTS, USER, - ASSISTANT, - NEWS_INTERESTS, + ASSISTANT, GET_REALTIME_TOKEN + GET_REALTIME_TOKEN, + + NEWS_INTERESTS, NEWS ).any { it in path } } } + install(io.ktor.client.plugins.websocket.WebSockets) } } @@ -159,4 +163,10 @@ object NetworkModule { return QuizServiceImpl(client) } + @Provides + @Singleton + fun provideRealTimeService(client: HttpClient): RealTimeService { + return RealTimeServiceImpl(client) + } + } \ No newline at end of file diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeService.kt b/data/src/main/java/com/saegil/data/remote/RealTimeService.kt new file mode 100644 index 00000000..f3493377 --- /dev/null +++ b/data/src/main/java/com/saegil/data/remote/RealTimeService.kt @@ -0,0 +1,5 @@ +package com.saegil.data.remote + +interface RealTimeService { + suspend fun connectToRealtimeSession(secret: String) +} diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt new file mode 100644 index 00000000..03922174 --- /dev/null +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -0,0 +1,51 @@ +package com.saegil.data.remote + +import com.saegil.data.BuildConfig +import io.ktor.client.HttpClient +import io.ktor.client.plugins.websocket.webSocket +import io.ktor.client.request.header +import io.ktor.client.request.url +import io.ktor.client.utils.EmptyContent.headers +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.headers +import io.ktor.websocket.Frame +import io.ktor.websocket.readText +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class RealTimeServiceImpl ( + private val client: HttpClient +) : RealTimeService { + + override suspend fun connectToRealtimeSession(secret: String) { + try { + client.webSocket( + request = { + url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17") +// header(HttpHeaders.Authorization, "Bearer ${BuildConfig.OPEN_AI_API_KEY}") + header(HttpHeaders.Authorization, "Bearer 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") + header("OpenAI-Beta", "realtime=v1") + } + ) { + println("✅ WebSocket connected") + + val initMessage = buildJsonObject { + put("type", "start") + } + send(Frame.Text(initMessage.toString())) + + for (frame in incoming) { + if (frame is Frame.Text) { + println("📨 Received: ${frame.readText()}") + } + } + } + } catch (e: Exception) { + println("❌ WebSocket error: ${e.message}") + e.printStackTrace() + } + } + + +} diff --git a/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt b/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt new file mode 100644 index 00000000..6a08a6cb --- /dev/null +++ b/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt @@ -0,0 +1,13 @@ +package com.saegil.data.repository + +import com.saegil.data.remote.RealTimeService +import com.saegil.domain.repository.RealTimeRepository + +class RealTimeRepositoryImpl ( + private val service: RealTimeService +) : RealTimeRepository { + + override suspend fun connect(secret: String) { + service.connectToRealtimeSession(secret) + } +} diff --git a/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt b/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt new file mode 100644 index 00000000..d0d96b75 --- /dev/null +++ b/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt @@ -0,0 +1,5 @@ +package com.saegil.domain.repository + +interface RealTimeRepository { + suspend fun connect(secret: String) +} diff --git a/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt b/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt new file mode 100644 index 00000000..402e0024 --- /dev/null +++ b/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt @@ -0,0 +1,12 @@ +package com.saegil.domain.usecase + +import com.saegil.domain.repository.RealTimeRepository +import javax.inject.Inject + +class StartRealtimeChatUseCase @Inject constructor( + private val repository: RealTimeRepository +) { + suspend operator fun invoke(clientSecret: String) { + repository.connect(clientSecret) + } +} diff --git a/presentation/ai_conversation/build.gradle.kts b/presentation/ai_conversation/build.gradle.kts index 14323a5f..5c4c1fa8 100644 --- a/presentation/ai_conversation/build.gradle.kts +++ b/presentation/ai_conversation/build.gradle.kts @@ -71,4 +71,6 @@ dependencies { implementation(project(":domain")) implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("io.ktor:ktor-client-websockets:2.3.5") + } \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt index 7b0fcc2e..a1de3a0d 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt @@ -12,10 +12,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusModifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -25,6 +25,7 @@ import com.saegil.ai_conversation.R import com.saegil.ai_conversation.SaegilCharacter import com.saegil.designsystem.theme.SaegilAndroidTheme import com.saegil.designsystem.theme.h1 +import kotlin.reflect.KSuspendFunction0 @Composable fun AiConversationScreen( @@ -37,7 +38,9 @@ fun AiConversationScreen( InternalAiConversationScreen( state = state, - modifier = modifier + modifier = modifier, +// onRequestToken = viewModel::onRequestToken, + startRealtimeChat = viewModel::startChatSession ) } @@ -45,8 +48,19 @@ fun AiConversationScreen( @Composable internal fun InternalAiConversationScreen( state: AiConversationState, - modifier: Modifier + modifier: Modifier, +// onRequestToken: + startRealtimeChat: (String) -> Unit = {}, ) { + +// LaunchedEffect(Unit) { +// val token = viewModel.onRequestToken() +// Log.d("UI", token) +// } + +// val token: String = onRequestToken().toString() +// startRealtimeChat(token.toString()) + Surface( modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt index 7eb750ca..fea76bf4 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt @@ -1,16 +1,24 @@ package com.saegil.ai_conversation.aiconversation +import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.saegil.domain.usecase.GetRealTimeTokenUsecase +import com.saegil.domain.usecase.StartRealtimeChatUseCase import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch @HiltViewModel class AiConversationViewModel @Inject constructor( - savedStateHandle: SavedStateHandle + savedStateHandle: SavedStateHandle, + val startRealtimeChatUseCase: StartRealtimeChatUseCase, + private val getRealtimeTokenUseCase: GetRealTimeTokenUsecase + ) : ViewModel() { private val _stateFlow: MutableStateFlow = @@ -18,7 +26,23 @@ class AiConversationViewModel @Inject constructor( val stateFlow: StateFlow = _stateFlow.asStateFlow() + init { + viewModelScope.launch { + val token = onRequestToken() + startChatSession(token) + } + } + suspend fun onRequestToken(): String { + Log.d("AiConversationListViewModel", "onRequestToken called") + val result = getRealtimeTokenUseCase() + Log.d("result", result) + return result + } -} + fun startChatSession(secret: String) { + viewModelScope.launch { + startRealtimeChatUseCase(secret) + } + }} class AiConversationState \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt index b939732d..c5692bce 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt @@ -36,9 +36,7 @@ fun AiConversationListScreen( modifier = modifier, onCharacterClick = { character -> onCharacterClick(character) - viewModel.onRequestToken() }, - getToken = viewModel::onRequestToken ) } @@ -47,7 +45,6 @@ internal fun InternalAiConversationListScreen( aiConversationListState: AiConversationListState, modifier: Modifier = Modifier, onCharacterClick: (String) -> Unit = { character -> }, - getToken: () -> Unit = {}, // onScenarioClick: (Long, String) -> Unit = { id, name -> }, ) { Surface( @@ -67,14 +64,12 @@ internal fun InternalAiConversationListScreen( onClick = { Log.d("screen character", SaegilCharacter.SAEROM.name) onCharacterClick(SaegilCharacter.SAEROM.name) - getToken() }) Spacer(modifier = Modifier.height(10.dp)) CharacterCard(modifier = Modifier.clickable { onCharacterClick(SaegilCharacter.GILDONG.name) - getToken() }, SaegilCharacter.GILDONG) diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt index ca79749d..d5ba4f36 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.launch @HiltViewModel class AiConversationListViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val getRealtimeTokenUseCase: GetRealTimeTokenUsecase ) : ViewModel() { @@ -25,13 +24,6 @@ class AiConversationListViewModel @Inject constructor( val stateFlow: StateFlow = _stateFlow.asStateFlow() - fun onRequestToken() { - Log.d("AiConversationListViewModel", "onRequestToken called") - viewModelScope.launch { - val result = getRealtimeTokenUseCase() - Log.d("result", "${result}") - } - } } diff --git a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt index b9375680..015f5767 100644 --- a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt +++ b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt @@ -294,59 +294,6 @@ fun RecordButton( } - -@Serializable -data class StartMessage( - val type: String = "start", - val model: String = "gpt-4o", - val messages: List -) - -@Serializable -data class Message( - val role: String, - val content: String -) - -fun startOpenAIWebSocket(apiKey: String) { - val client = HttpClient(CIO) { - install(WebSockets) - } - - CoroutineScope(Dispatchers.IO).launch { - client.webSocket( - method = HttpMethod.Get, - host = "api.openai.com", - path = "/v1/realtime", - request = { - header("Authorization", "Bearer $apiKey") - header("Content-Type", "application/json") - } - ) { - println("✅ WebSocket 연결 성공") - - // JSON 메시지 생성 - val message = StartMessage( - messages = listOf( - Message("user", "Hello, how are you?") - ) - ) - val json = Json.encodeToString(message) - send(Frame.Text(json)) - - // 수신 루프 - for (frame in incoming) { - when (frame) { - is Frame.Text -> println("📥 수신: ${frame.readText()}") - is Frame.Close -> println("❌ 연결 종료됨") - else -> {} - } - } - } - } -} - - @Composable @Preview(name = "Learning", apiLevel = 33) private fun LearningScreenPreview() { From c5224bae44f280e088c7c81367224d972fe77d36 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 00:51:48 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20audio=20=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=ED=86=B5=EC=8B=A0=20(+=EC=97=B0=EA=B2=B0,=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/saegil/android/navigation/NavGraph.kt | 4 +- .../data/remote/RealTimeMessageSender.kt | 58 +++++++++ .../com/saegil/data/remote/RealTimeService.kt | 6 + .../saegil/data/remote/RealTimeServiceImpl.kt | 117 ++++++++++++++++-- .../data/repository/RealTimeRepositoryImpl.kt | 12 ++ .../domain/repository/RealTimeRepository.kt | 7 ++ .../domain/usecase/EndRealtimeChatUseCase.kt | 10 ++ .../usecase/StartRealtimeChatUseCase.kt | 9 ++ .../aiconversation/AiConversationScreen.kt | 32 ++++- .../aiconversation/AiConversationViewModel.kt | 73 ++++++++++- .../learning/learning/LearningScreen.kt | 2 +- 11 files changed, 313 insertions(+), 17 deletions(-) create mode 100644 data/src/main/java/com/saegil/data/remote/RealTimeMessageSender.kt create mode 100644 domain/src/main/java/com/saegil/domain/usecase/EndRealtimeChatUseCase.kt diff --git a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt index 08074db1..ec9ba474 100644 --- a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt +++ b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt @@ -49,7 +49,9 @@ fun NavGraph(navController: NavHostController, modifier: Modifier) { val characterString = backStackEntry.arguments?.getString("characterString") val character = runCatching { SaegilCharacter.valueOf(characterString ?: "") }.getOrNull() - AiConversationScreen(character = character) + AiConversationScreen(character = character, + navigateToAiConversationList = { navController.popBackStack() } + ) } composable(Screen.Learning.route) { diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeMessageSender.kt b/data/src/main/java/com/saegil/data/remote/RealTimeMessageSender.kt new file mode 100644 index 00000000..f354a945 --- /dev/null +++ b/data/src/main/java/com/saegil/data/remote/RealTimeMessageSender.kt @@ -0,0 +1,58 @@ +package com.saegil.data.remote + +import android.os.Build +import androidx.annotation.RequiresApi +import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession +import io.ktor.websocket.Frame +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject +import java.util.Base64 + +object RealtimeMessageSender { + + suspend fun sendUserTextMessage( + client: DefaultClientWebSocketSession, + message: String + ) { + val createMessage = buildJsonObject { + put("type", "conversation.item.create") + putJsonObject("item") { + put("type", "message") + put("role", "user") + putJsonArray("content") { + addJsonObject { + put("type", "input_text") + put("text", message) + } + } + } + } + + val createResponse = buildJsonObject { + put("type", "response.create") + } + + client.send(Frame.Text(createMessage.toString())) + client.send(Frame.Text(createResponse.toString())) + } + + @RequiresApi(Build.VERSION_CODES.O) + suspend fun sendPcmAudio(client: DefaultClientWebSocketSession, pcm: ByteArray) { + val base64Audio = Base64.getEncoder().encodeToString(pcm) + val appendFrame = buildJsonObject { + put("type", "input_audio_buffer.append") + put("audio", base64Audio) + } + client.send(Frame.Text(appendFrame.toString())) + } + + suspend fun commitAudio(client: DefaultClientWebSocketSession) { + val commitFrame = buildJsonObject { + put("type", "input_audio_buffer.commit") + } + client.send(Frame.Text(commitFrame.toString())) + } +} diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeService.kt b/data/src/main/java/com/saegil/data/remote/RealTimeService.kt index f3493377..ef73435a 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeService.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeService.kt @@ -2,4 +2,10 @@ package com.saegil.data.remote interface RealTimeService { suspend fun connectToRealtimeSession(secret: String) + + suspend fun sendPcm(pcm: ByteArray) + + suspend fun commitAudio() + + suspend fun disconnect() } diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index 03922174..2f79adac 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -1,39 +1,52 @@ package com.saegil.data.remote +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi import com.saegil.data.BuildConfig import io.ktor.client.HttpClient +import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession import io.ktor.client.plugins.websocket.webSocket import io.ktor.client.request.header import io.ktor.client.request.url -import io.ktor.client.utils.EmptyContent.headers import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod -import io.ktor.http.headers import io.ktor.websocket.Frame +import io.ktor.websocket.close import io.ktor.websocket.readText -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch -class RealTimeServiceImpl ( +class RealTimeServiceImpl( private val client: HttpClient ) : RealTimeService { + private var session: DefaultClientWebSocketSession? = null + private var isStreaming = false + override suspend fun connectToRealtimeSession(secret: String) { try { client.webSocket( + method = HttpMethod.Get, + host = "api.openai.com", + path = "/v1/realtime/sessions/$secret", request = { - url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17") + url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17&modalities=audio") // header(HttpHeaders.Authorization, "Bearer ${BuildConfig.OPEN_AI_API_KEY}") header(HttpHeaders.Authorization, "Bearer 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") header("OpenAI-Beta", "realtime=v1") } ) { println("✅ WebSocket connected") + session = this + isStreaming = true - val initMessage = buildJsonObject { - put("type", "start") + launch(Dispatchers.IO) { + startAudioStreaming(this@webSocket) } - send(Frame.Text(initMessage.toString())) for (frame in incoming) { if (frame is Frame.Text) { @@ -43,9 +56,93 @@ class RealTimeServiceImpl ( } } catch (e: Exception) { println("❌ WebSocket error: ${e.message}") - e.printStackTrace() + } finally { + isStreaming = false } } + @RequiresApi(Build.VERSION_CODES.O) + override suspend fun sendPcm(pcm: ByteArray) { + session?.let { + RealtimeMessageSender.sendPcmAudio(it, pcm) + } ?: println("❌ No active WebSocket session to send PCM") + } + + override suspend fun commitAudio() { + session?.let { + RealtimeMessageSender.commitAudio(it) + } ?: println("❌ No active WebSocket session to commit audio") + } + @RequiresApi(Build.VERSION_CODES.O) + private fun startAudioStreaming(ws: DefaultClientWebSocketSession) { + val recorder = AudioRecord( + MediaRecorder.AudioSource.MIC, + SAMPLE_RATE, + CHANNEL_CONFIG, + AUDIO_FORMAT, + BUFFER_SIZE + ) + + if (recorder.state != AudioRecord.STATE_INITIALIZED) { + println("❌ AudioRecord 초기화 실패") + return + } + + val buffer = ByteArray(BUFFER_SIZE) + var appendCount = 0 + + recorder.startRecording() + println("🎙️ Audio recording started") + + while (isStreaming) { + val read = recorder.read(buffer, 0, buffer.size) + if (read > 0) { + val audioChunk = buffer.copyOf(read) + + CoroutineScope(Dispatchers.IO).launch { + RealtimeMessageSender.sendPcmAudio(ws, audioChunk) + } + + appendCount++ + } else { + println("⚠️ Audio read failed or empty (read=$read)") + } + + if (appendCount >= 10) { + CoroutineScope(Dispatchers.IO).launch { + RealtimeMessageSender.commitAudio(ws) + } + appendCount = 0 + } + } + + recorder.stop() + recorder.release() + + CoroutineScope(Dispatchers.IO).launch { + RealtimeMessageSender.commitAudio(ws) + } + + println("🎙️ Audio recording stopped and committed") + } + + companion object{ + private const val SAMPLE_RATE = 16000 // 16kHz + private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO // 모노 채널 + private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT // 16bit signed PCM + private val BUFFER_SIZE = AudioRecord.getMinBufferSize( + SAMPLE_RATE, + CHANNEL_CONFIG, + AUDIO_FORMAT + ).coerceAtLeast(SAMPLE_RATE * 2) // 1초 분량 or 최소값 이상 + + } + + override suspend fun disconnect() { + isStreaming = false + session?.close() + session = null + println("🔌 WebSocket disconnected") + } } diff --git a/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt b/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt index 6a08a6cb..e972014c 100644 --- a/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt +++ b/data/src/main/java/com/saegil/data/repository/RealTimeRepositoryImpl.kt @@ -10,4 +10,16 @@ class RealTimeRepositoryImpl ( override suspend fun connect(secret: String) { service.connectToRealtimeSession(secret) } + + override suspend fun sendPcm(pcm: ByteArray) { + service.sendPcm(pcm) + } + + override suspend fun commitAudio() { + service.commitAudio() + } + + override suspend fun disconnect() { + service.disconnect() + } } diff --git a/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt b/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt index d0d96b75..0bfa5dc9 100644 --- a/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt +++ b/domain/src/main/java/com/saegil/domain/repository/RealTimeRepository.kt @@ -2,4 +2,11 @@ package com.saegil.domain.repository interface RealTimeRepository { suspend fun connect(secret: String) + + suspend fun sendPcm(pcm: ByteArray) + + suspend fun commitAudio() + + suspend fun disconnect() + } diff --git a/domain/src/main/java/com/saegil/domain/usecase/EndRealtimeChatUseCase.kt b/domain/src/main/java/com/saegil/domain/usecase/EndRealtimeChatUseCase.kt new file mode 100644 index 00000000..0cd28a1b --- /dev/null +++ b/domain/src/main/java/com/saegil/domain/usecase/EndRealtimeChatUseCase.kt @@ -0,0 +1,10 @@ +package com.saegil.domain.usecase + +import com.saegil.domain.repository.RealTimeRepository +import javax.inject.Inject + +class EndRealtimeChatUseCase @Inject constructor( + private val repository: RealTimeRepository +) { + suspend operator fun invoke() = repository.disconnect() +} \ No newline at end of file diff --git a/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt b/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt index 402e0024..723dbf89 100644 --- a/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt +++ b/domain/src/main/java/com/saegil/domain/usecase/StartRealtimeChatUseCase.kt @@ -9,4 +9,13 @@ class StartRealtimeChatUseCase @Inject constructor( suspend operator fun invoke(clientSecret: String) { repository.connect(clientSecret) } + + + suspend fun sendPcm(pcm: ByteArray) { + repository.sendPcm(pcm) + } + + suspend fun commitAudio() { + repository.commitAudio() + } } diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt index a1de3a0d..fb976861 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt @@ -1,6 +1,10 @@ package com.saegil.ai_conversation.aiconversation +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -16,9 +20,12 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.saegil.ai_conversation.R @@ -32,6 +39,7 @@ fun AiConversationScreen( character: SaegilCharacter?, modifier: Modifier = Modifier, viewModel: AiConversationViewModel = hiltViewModel(), + navigateToAiConversationList: () -> Unit = {}, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -40,7 +48,9 @@ fun AiConversationScreen( state = state, modifier = modifier, // onRequestToken = viewModel::onRequestToken, - startRealtimeChat = viewModel::startChatSession +// startRealtimeChat = viewModel::startChatSession, + onStopButtonClick = viewModel::stopChatSession, + navigateToAiConversationList = navigateToAiConversationList, ) } @@ -49,6 +59,8 @@ fun AiConversationScreen( internal fun InternalAiConversationScreen( state: AiConversationState, modifier: Modifier, + onStopButtonClick: () -> Unit = {}, + navigateToAiConversationList: () -> Unit = {}, // onRequestToken: startRealtimeChat: (String) -> Unit = {}, ) { @@ -60,7 +72,18 @@ internal fun InternalAiConversationScreen( // val token: String = onRequestToken().toString() // startRealtimeChat(token.toString()) + val context = LocalContext.current + if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) + != PackageManager.PERMISSION_GRANTED + ) { + + ActivityCompat.requestPermissions( + context as Activity, // 여기가 중요! + arrayOf(Manifest.permission.RECORD_AUDIO), + 1001 + ) + } Surface( modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background @@ -91,7 +114,12 @@ internal fun InternalAiConversationScreen( Image( painterResource(R.drawable.img_btn_end_call), - modifier = Modifier.size(96.dp), + modifier = Modifier + .size(96.dp) + .clickable { + onStopButtonClick() + navigateToAiConversationList() + }, contentDescription = "전화 종료" ) } diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt index fea76bf4..9a15480a 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationViewModel.kt @@ -1,12 +1,17 @@ package com.saegil.ai_conversation.aiconversation +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaRecorder import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.saegil.domain.usecase.EndRealtimeChatUseCase import com.saegil.domain.usecase.GetRealTimeTokenUsecase import com.saegil.domain.usecase.StartRealtimeChatUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -17,7 +22,8 @@ import kotlinx.coroutines.launch class AiConversationViewModel @Inject constructor( savedStateHandle: SavedStateHandle, val startRealtimeChatUseCase: StartRealtimeChatUseCase, - private val getRealtimeTokenUseCase: GetRealTimeTokenUsecase + private val getRealtimeTokenUseCase: GetRealTimeTokenUsecase, + private val disconnectRealtimeUseCase: EndRealtimeChatUseCase ) : ViewModel() { @@ -43,6 +49,67 @@ class AiConversationViewModel @Inject constructor( viewModelScope.launch { startRealtimeChatUseCase(secret) } - }} + } + + + fun stopChatSession() { + viewModelScope.launch { + disconnectRealtimeUseCase() + } + } + + private var audioRecord: AudioRecord? = null + private var isRecording = false + + fun startRecordingAndSendAudio() { + audioRecord = AudioRecord( + MediaRecorder.AudioSource.MIC, + SAMPLE_RATE, + CHANNEL_CONFIG, + AUDIO_FORMAT, + BUFFER_SIZE + ) + + isRecording = true + audioRecord?.startRecording() + + viewModelScope.launch(Dispatchers.IO) { + val buffer = ByteArray(BUFFER_SIZE) + + while (isRecording) { + val readBytes = audioRecord?.read(buffer, 0, buffer.size) ?: 0 + if (readBytes > 0) { + val pcmData = buffer.copyOf(readBytes) + startRealtimeChatUseCase.sendPcm(pcmData) + } + } + + // 커밋 + startRealtimeChatUseCase.commitAudio() + } + } + + fun stopRecording() { + isRecording = false + audioRecord?.stop() + audioRecord?.release() + audioRecord = null + } + +companion object{ + private const val SAMPLE_RATE = 16000 // 16kHz + private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO // 모노 채널 + private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT // 16bit signed PCM + private val BUFFER_SIZE = AudioRecord.getMinBufferSize( + SAMPLE_RATE, + CHANNEL_CONFIG, + AUDIO_FORMAT + ).coerceAtLeast(SAMPLE_RATE * 2) // 1초 분량 or 최소값 이상 + +} +} + + + +class AiConversationState -class AiConversationState \ No newline at end of file diff --git a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt index 015f5767..b3aa18fa 100644 --- a/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt +++ b/presentation/learning/src/main/java/com/saegil/learning/learning/LearningScreen.kt @@ -66,7 +66,7 @@ fun LearningScreen( navigateToLearningList: () -> Unit = {}, scenarioId: Long, scenarioName: String = "", - viewModel: LearningViewModel = hiltViewModel() + viewModel: LearningViewModel = hiltViewModel(), ) { val state by viewModel.uiState.collectAsState() val context = LocalContext.current From 62828fa3a8fcf7854eb4d0ee4a6690cbe53f0615 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 01:25:09 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=EC=86=8C=EC=BC=93=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=20=EC=98=A4=EB=94=94=EC=98=A4=20=EC=9E=AC=EC=83=9D=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 --- .../saegil/data/remote/RealTimeServiceImpl.kt | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index 2f79adac..5654ba7a 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -1,7 +1,9 @@ package com.saegil.data.remote import android.media.AudioFormat +import android.media.AudioManager import android.media.AudioRecord +import android.media.AudioTrack import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi @@ -19,6 +21,10 @@ import io.ktor.websocket.readText import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.util.Base64 class RealTimeServiceImpl( private val client: HttpClient @@ -27,6 +33,7 @@ class RealTimeServiceImpl( private var session: DefaultClientWebSocketSession? = null private var isStreaming = false + @RequiresApi(Build.VERSION_CODES.O) override suspend fun connectToRealtimeSession(secret: String) { try { client.webSocket( @@ -50,7 +57,52 @@ class RealTimeServiceImpl( for (frame in incoming) { if (frame is Frame.Text) { - println("📨 Received: ${frame.readText()}") + val json = frame.readText() + println("📨 Received: $json") + + val jsonObj = Json.parseToJsonElement(json).jsonObject + when (jsonObj["type"]?.jsonPrimitive?.content) { + "session.created" -> { + // Handle session creation confirmation. + // You might want to store the session ID here. + val sessionId = jsonObj["session"]?.jsonObject?.get("id")?.jsonPrimitive?.content + println("Session created with ID: $sessionId") + } + // This is the crucial part: handling the audio delta messages + "response.audio.delta" -> { + // Extract the "delta" field which contains the Base64 encoded audio bytes + val base64Audio = jsonObj["delta"]?.jsonPrimitive?.content ?: "" + if (base64Audio.isNotEmpty()) { + try { + // Decode the Base64 string to a ByteArray + val audioBytes = Base64.getDecoder().decode(base64Audio) + // Write the audio bytes to the AudioTrack for playback + playAudio(audioBytes) + } catch (e: IllegalArgumentException) { + println("❌ Base64 decoding error: ${e.message}") + } + } + } + "response.completed" -> { + // The AI's response has completed. + // You might want to signal the end of speech here, + // but keep the AudioTrack playing until its buffer is empty. + println("AI response completed.") + } + // Handle other message types if necessary (e.g., input_audio_buffer.speech_started/stopped) + else -> { + // println("Received message of type: ${jsonObj["type"]?.jsonPrimitive?.content}") + } +// "audio" -> { +// val base64Audio = jsonObj["data"]?.jsonPrimitive?.content ?: "" +// val audioBytes = Base64.getDecoder().decode(base64Audio) +// playAudio(audioBytes) +// } +// else -> { +// // 다른 메시지 타입 처리 (예: session.created, error 등) +//// println("Received message but not audio: $json") +// } + } } } } @@ -73,6 +125,7 @@ class RealTimeServiceImpl( RealtimeMessageSender.commitAudio(it) } ?: println("❌ No active WebSocket session to commit audio") } + @RequiresApi(Build.VERSION_CODES.O) private fun startAudioStreaming(ws: DefaultClientWebSocketSession) { val recorder = AudioRecord( @@ -126,6 +179,23 @@ class RealTimeServiceImpl( println("🎙️ Audio recording stopped and committed") } + private fun playAudio(audio: ByteArray) { + val audioTrack = AudioTrack( + AudioManager.STREAM_MUSIC, + SAMPLE_RATE, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, + audio.size, + AudioTrack.MODE_STATIC + ) + + audioTrack.write(audio, 0, audio.size) + audioTrack.play() + + println("🔊 Audio playback started") + } + + companion object{ private const val SAMPLE_RATE = 16000 // 16kHz private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO // 모노 채널 From c2e6f6bcce0f21fc71a4a4a6da30231846599b12 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 02:08:37 +0900 Subject: [PATCH 05/15] =?UTF-8?q?fix:=20=ED=81=90=EC=97=90=20=EB=84=A3?= =?UTF-8?q?=EC=96=B4=EC=84=9C=20=EC=98=A4=EB=94=94=EC=98=A4=20=EA=B2=B9?= =?UTF-8?q?=EC=B9=A8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saegil/data/remote/RealTimeServiceImpl.kt | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index 5654ba7a..2965da13 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -179,20 +179,56 @@ class RealTimeServiceImpl( println("🎙️ Audio recording stopped and committed") } + private var audioTrack: AudioTrack? = null + private var isAudioPlaying = false + private val audioQueue: ArrayDeque = ArrayDeque() + private fun playAudio(audio: ByteArray) { - val audioTrack = AudioTrack( + // 재생 중이면 큐에 추가만 + if (isAudioPlaying) { + audioQueue.addLast(audio) + println("📥 Queued audio chunk. Queue size: ${audioQueue.size}") + return + } + + // 다음 오디오 재생 시작 + audioTrack?.release() + + val track = AudioTrack( AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, audio.size, - AudioTrack.MODE_STATIC + AudioTrack.MODE_STATIC, + AudioManager.AUDIO_SESSION_ID_GENERATE ) - audioTrack.write(audio, 0, audio.size) - audioTrack.play() + track.setNotificationMarkerPosition(audio.size / 2) + track.setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener { + override fun onMarkerReached(track: AudioTrack?) { + isAudioPlaying = false + println("✅ Finished audio chunk") + + // 다음 오디오가 있다면 재생 + if (audioQueue.isNotEmpty()) { + val nextAudio = audioQueue.removeFirst() + playAudio(nextAudio) + } + } + + override fun onPeriodicNotification(track: AudioTrack?) { + // Not used + } + }) + + audioTrack = track + isAudioPlaying = true + + track.write(audio, 0, audio.size) + track.play() - println("🔊 Audio playback started") + println("🔊 Playing audio chunk. Queue size: ${audioQueue.size}") } From 82f1e23945d9b5960666e797ab044caae91950c3 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 02:10:13 +0900 Subject: [PATCH 06/15] =?UTF-8?q?fix:=20=EC=98=A4=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20=EC=A2=80=EB=8D=94=20=EC=9E=90=EC=97=B0?= =?UTF-8?q?=EC=8A=A4=EB=9F=BD=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saegil/data/remote/RealTimeServiceImpl.kt | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index 2965da13..37409bae 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -183,55 +183,45 @@ class RealTimeServiceImpl( private var isAudioPlaying = false private val audioQueue: ArrayDeque = ArrayDeque() - private fun playAudio(audio: ByteArray) { - // 재생 중이면 큐에 추가만 - if (isAudioPlaying) { - audioQueue.addLast(audio) - println("📥 Queued audio chunk. Queue size: ${audioQueue.size}") - return + private fun initializeAudioTrackIfNeeded() { + if (audioTrack == null) { + audioTrack = AudioTrack( + AudioManager.STREAM_MUSIC, + SAMPLE_RATE, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, + BUFFER_SIZE, + AudioTrack.MODE_STREAM, + AudioManager.AUDIO_SESSION_ID_GENERATE + ).apply { + play() + println("🔄 AudioTrack initialized") + } } + } - // 다음 오디오 재생 시작 - audioTrack?.release() - - val track = AudioTrack( - AudioManager.STREAM_MUSIC, - SAMPLE_RATE, - AudioFormat.CHANNEL_OUT_MONO, - AudioFormat.ENCODING_PCM_16BIT, - audio.size, - AudioTrack.MODE_STATIC, - AudioManager.AUDIO_SESSION_ID_GENERATE - ) + private fun playAudio(audio: ByteArray) { + audioQueue.addLast(audio) + println("📥 Queued audio chunk. Queue size: ${audioQueue.size}") - track.setNotificationMarkerPosition(audio.size / 2) - track.setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener { - override fun onMarkerReached(track: AudioTrack?) { - isAudioPlaying = false - println("✅ Finished audio chunk") + if (!isAudioPlaying) { + isAudioPlaying = true + CoroutineScope(Dispatchers.IO).launch { + initializeAudioTrackIfNeeded() - // 다음 오디오가 있다면 재생 - if (audioQueue.isNotEmpty()) { - val nextAudio = audioQueue.removeFirst() - playAudio(nextAudio) + while (audioQueue.isNotEmpty()) { + val chunk = audioQueue.removeFirst() + audioTrack?.write(chunk, 0, chunk.size) } - } - override fun onPeriodicNotification(track: AudioTrack?) { - // Not used + // wait for buffer to play + audioTrack?.flush() + isAudioPlaying = false + println("✅ Finished playing all queued audio.") } - }) - - audioTrack = track - isAudioPlaying = true - - track.write(audio, 0, audio.size) - track.play() - - println("🔊 Playing audio chunk. Queue size: ${audioQueue.size}") + } } - companion object{ private const val SAMPLE_RATE = 16000 // 16kHz private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO // 모노 채널 From 538290ef8df105ca9255d9b8d333cf6d4995a2a8 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 02:30:44 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=94=94=ED=85=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiconversation/AiConversationScreen.kt | 61 ++++++++++++------ .../main/res/drawable/img_btn_end_call.png | Bin 23965 -> 42453 bytes 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt index fb976861..8e35c28e 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt @@ -4,14 +4,18 @@ import android.Manifest import android.app.Activity import android.content.pm.PackageManager 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.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -20,6 +24,8 @@ 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.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -31,8 +37,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.saegil.ai_conversation.R import com.saegil.ai_conversation.SaegilCharacter import com.saegil.designsystem.theme.SaegilAndroidTheme +import com.saegil.designsystem.theme.body2 import com.saegil.designsystem.theme.h1 -import kotlin.reflect.KSuspendFunction0 +import com.saegil.designsystem.theme.h3 @Composable fun AiConversationScreen( @@ -47,8 +54,6 @@ fun AiConversationScreen( InternalAiConversationScreen( state = state, modifier = modifier, -// onRequestToken = viewModel::onRequestToken, -// startRealtimeChat = viewModel::startChatSession, onStopButtonClick = viewModel::stopChatSession, navigateToAiConversationList = navigateToAiConversationList, ) @@ -61,17 +66,8 @@ internal fun InternalAiConversationScreen( modifier: Modifier, onStopButtonClick: () -> Unit = {}, navigateToAiConversationList: () -> Unit = {}, -// onRequestToken: - startRealtimeChat: (String) -> Unit = {}, ) { -// LaunchedEffect(Unit) { -// val token = viewModel.onRequestToken() -// Log.d("UI", token) -// } - -// val token: String = onRequestToken().toString() -// startRealtimeChat(token.toString()) val context = LocalContext.current if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) @@ -85,18 +81,41 @@ internal fun InternalAiConversationScreen( ) } Surface( - modifier = modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) - { + modifier = modifier + .fillMaxSize() + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color(0xFFE7F2FF), // 시작 색상 (연한 하늘색) + Color(0xFFFFFFFF) // 끝 색상 (흰색) + ) + ) + ), + color = Color.Transparent + ) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("전화 거는 중...") - Spacer(modifier = Modifier.height(10.dp)) - Text("동갑내기 친구와 \"반말로\" 편안하게 대화해보세요!") - Spacer(modifier = Modifier.height(10.dp)) + Text("전화 거는 중...", + style = MaterialTheme.typography.h3, + color = MaterialTheme.colorScheme.onBackground) + Spacer(modifier = Modifier.height(18.dp)) + Box( + Modifier.border( + 1.dp, + color = MaterialTheme.colorScheme.primary, + shape = RoundedCornerShape(4.dp) // ← 코너를 4dp만큼 둥글게 + ) + ) { + Text( + "동갑내기 친구와 \"반말로\" 편안하게 대화해보세요!", + modifier = Modifier.padding(10.dp), + style = MaterialTheme.typography.body2 + ) + + } + Spacer(modifier = Modifier.height(60.dp)) Image( painterResource(R.drawable.img_saerom), @@ -108,7 +127,7 @@ internal fun InternalAiConversationScreen( "새롬", color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.h1, - modifier = Modifier.padding(top = 10.dp) + modifier = Modifier.padding(top = 18.dp) ) Spacer(modifier = Modifier.height(120.dp)) diff --git a/presentation/ai_conversation/src/main/res/drawable/img_btn_end_call.png b/presentation/ai_conversation/src/main/res/drawable/img_btn_end_call.png index c06f7c6946b3a1b5ac20e165edfc3f8fcba580cb..9d950815961ed24f9923a46330ad367776fa5e5a 100644 GIT binary patch literal 42453 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 mcpd)suG9T7z|g?!#{U7KWF1HJYE1b60000ojW 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 CV=|op From 266fbeaf7c712a24b8b9b194d5078d2a3de47939 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 02:50:05 +0900 Subject: [PATCH 08/15] =?UTF-8?q?feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=94=94=ED=85=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AiConversationListScreen.kt | 69 +++++---------- .../components/CharacterCard.kt | 84 ++++++++++++------- 2 files changed, 75 insertions(+), 78 deletions(-) diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt index c5692bce..283598ed 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/AiConversationListScreen.kt @@ -8,6 +8,9 @@ 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.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -29,70 +32,38 @@ fun AiConversationListScreen( onCharacterClick: (String) -> Unit = { character -> }, viewModel: AiConversationListViewModel = hiltViewModel(), ) { - val learningListState by viewModel.stateFlow.collectAsStateWithLifecycle() - InternalAiConversationListScreen( - aiConversationListState = learningListState, - modifier = modifier, - onCharacterClick = { character -> - onCharacterClick(character) - }, - ) -} - -@Composable -internal fun InternalAiConversationListScreen( - aiConversationListState: AiConversationListState, - modifier: Modifier = Modifier, - onCharacterClick: (String) -> Unit = { character -> }, -// onScenarioClick: (Long, String) -> Unit = { id, name -> }, -) { Surface( modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - Column { + Column( + Modifier + .padding() + .verticalScroll(rememberScrollState()) + ) { SaegilTitleText( "AI 전화 회화", modifier = Modifier.align(Alignment.CenterHorizontally) ) - Spacer(modifier = Modifier.height(60.dp)) + Spacer(modifier = Modifier.height(30.dp)) + CharacterCard( modifier = Modifier, - SaegilCharacter.SAEROM, + character = SaegilCharacter.SAEROM, onClick = { - Log.d("screen character", SaegilCharacter.SAEROM.name) onCharacterClick(SaegilCharacter.SAEROM.name) }) - Spacer(modifier = Modifier.height(10.dp)) - - CharacterCard(modifier = Modifier.clickable { - onCharacterClick(SaegilCharacter.GILDONG.name) - }, SaegilCharacter.GILDONG) - + Spacer(modifier = Modifier.height(30.dp)) -// when (aiConversationListState) { -// is LearningListUiState.Loading -> { -// // TODO: Show loading state -// } -// is LearningListUiState.Success -> { -// LazyColumn( -// modifier = Modifier.fillMaxSize(), -// contentPadding = PaddingValues(36.dp), -// verticalArrangement = Arrangement.spacedBy(12.dp) -// ) { -// items(learningListState.organizationList) { item -> -// ScenarioItem( -// name = item.name, -// iconImageUrl = item.iconImageUrl, -// onClick = { onScenarioClick(item.id, item.name) } -// ) -// } -// } -// } -// } + CharacterCard(modifier = Modifier, + character = SaegilCharacter.GILDONG, + onClick = { + onCharacterClick(SaegilCharacter.GILDONG.name) + } + ) } } } @@ -102,9 +73,7 @@ internal fun InternalAiConversationListScreen( private fun LearningScreenPreview() { SaegilAndroidTheme { Surface { - InternalAiConversationListScreen( - aiConversationListState = AiConversationListState() - ) + AiConversationListScreen() } } } diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt index 33ef946d..a199a6b7 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversationlist/components/CharacterCard.kt @@ -1,13 +1,17 @@ package com.saegil.ai_conversation.aiconversationlist.components 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.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -19,44 +23,66 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.saegil.ai_conversation.SaegilCharacter +import com.saegil.designsystem.theme.SaegilAndroidTheme import com.saegil.designsystem.theme.body2 import com.saegil.designsystem.theme.h2 @Composable fun CharacterCard( - modifier: Modifier=Modifier, + modifier: Modifier = Modifier, character: SaegilCharacter, onClick: () -> Unit = {} ) { - Card(modifier = modifier.clickable { -// onClick() - } - + Box( + modifier = modifier + .padding(horizontal = 24.dp) + .background(MaterialTheme.colorScheme.background) + .border( + 1.dp, + color = MaterialTheme.colorScheme.scrim, + shape = RoundedCornerShape(4.dp) // ← 코너를 4dp만큼 둥글게 + ) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Image(painterResource(character.img), contentDescription = "새봄 프로필 사진") - - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Center, + Column(horizontalAlignment = Alignment.Start) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - Text(character.nickname, style = MaterialTheme.typography.h2, color = Color.Blue) - Spacer(modifier = Modifier.height(12.dp)) - Text(character.gender, style = MaterialTheme.typography.body2) - Spacer(modifier = Modifier.height(4.dp)) - Text(character.personality, style = MaterialTheme.typography.body2) - Spacer(modifier = Modifier.height(4.dp)) - Text(character.description, style = MaterialTheme.typography.body2) + Image( + painterResource(character.img), contentDescription = "새봄 프로필 사진", + modifier = Modifier.padding( + start = 24.dp, + top = 24.dp, + bottom = 18.dp, + end = 30.dp + ) + ) + + Spacer(modifier = Modifier.height(30.dp)) + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + ) { + Text( + character.nickname, + style = MaterialTheme.typography.h2, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(12.dp)) + Text(character.gender, style = MaterialTheme.typography.body2) + Spacer(modifier = Modifier.height(4.dp)) + Text(character.personality, style = MaterialTheme.typography.body2) + Spacer(modifier = Modifier.height(4.dp)) + Text(character.description, style = MaterialTheme.typography.body2) + } } - } - CallButton(onClick = { - onClick() - }, modifier = Modifier.padding(20.dp)) + CallButton(onClick = { + onClick() + }, modifier = Modifier.padding(20.dp)) + } } } @@ -64,9 +90,11 @@ fun CharacterCard( @Preview @Composable fun CharacterCardPreview() { - Column { - CharacterCard(modifier = Modifier, SaegilCharacter.SAEROM) + SaegilAndroidTheme { + Column { + CharacterCard(modifier = Modifier, SaegilCharacter.SAEROM) - CharacterCard(modifier = Modifier, SaegilCharacter.GILDONG) + CharacterCard(modifier = Modifier, SaegilCharacter.GILDONG) + } } } \ No newline at end of file From b7756ebfd9fdb3dc63e90e0e46111ca0e89a7da4 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 02:56:59 +0900 Subject: [PATCH 09/15] =?UTF-8?q?fix:=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/saegil/android/MainActivity.kt | 5 +++-- app/src/main/java/com/saegil/android/navigation/NavGraph.kt | 2 +- app/src/main/java/com/saegil/android/navigation/Screen.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/saegil/android/MainActivity.kt b/app/src/main/java/com/saegil/android/MainActivity.kt index 421eea85..aae2ff15 100644 --- a/app/src/main/java/com/saegil/android/MainActivity.kt +++ b/app/src/main/java/com/saegil/android/MainActivity.kt @@ -45,10 +45,11 @@ fun MainScreen() { Screen.LogList.route, Screen.Log.route, ) || listOf( - Screen.Learning.route, +// Screen.Learning.route, Screen.Log.route, Screen.LogList.route, - ).any { prefix -> currentRoute?.startsWith("$prefix/") == true } + Screen.AiConversation.route, + ).any { prefix -> currentRoute?.startsWith("$prefix/") == true } Scaffold( topBar = { diff --git a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt index ec9ba474..9a8d0c99 100644 --- a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt +++ b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt @@ -155,7 +155,7 @@ fun NavGraph(navController: NavHostController, modifier: Modifier) { } }, navigateToMain = { - navController.navigate(Screen.Learning.route) { + navController.navigate(Screen.AiConversation.route) { popUpTo(Screen.Splash.route) { inclusive = true } } }, diff --git a/app/src/main/java/com/saegil/android/navigation/Screen.kt b/app/src/main/java/com/saegil/android/navigation/Screen.kt index 199f52c0..ea48173b 100644 --- a/app/src/main/java/com/saegil/android/navigation/Screen.kt +++ b/app/src/main/java/com/saegil/android/navigation/Screen.kt @@ -18,6 +18,6 @@ sealed class Screen(val route: String, @DrawableRes val icon: Int?, val label: S data object Quiz : Screen("quiz", null, "퀴즈") companion object { - val items = listOf(AiConversation,Learning, Announcement, News, MyPage) + val items = listOf(AiConversation, Announcement, News, MyPage) } } \ No newline at end of file From 087189514e22d900c0487d7b0551c0dce6dd5e7d Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 03:06:43 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20Ai=20=EC=A0=84=ED=99=94=20?= =?UTF-8?q?=ED=9A=8C=ED=99=94=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=94=94=ED=85=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/saegil/android/navigation/Screen.kt | 2 +- .../saegil/ai_conversation/SaegilCharacter.kt | 9 ++++++--- .../aiconversation/AiConversationScreen.kt | 18 +++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/saegil/android/navigation/Screen.kt b/app/src/main/java/com/saegil/android/navigation/Screen.kt index ea48173b..c7dcb459 100644 --- a/app/src/main/java/com/saegil/android/navigation/Screen.kt +++ b/app/src/main/java/com/saegil/android/navigation/Screen.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import com.saegil.android.R sealed class Screen(val route: String, @DrawableRes val icon: Int?, val label: String) { - object AiConversation : Screen("ai_conversation", R.drawable.ic_pencil, "AI 대화") + object AiConversation : Screen("ai_conversation", R.drawable.ic_pencil, "AI 전화 회화") object Learning : Screen("learning", R.drawable.ic_pencil, "학습") object Announcement : Screen("announcement", R.drawable.ic_announcement, "공지사항") diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt index 10d6b4fc..433d8f54 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/SaegilCharacter.kt @@ -7,20 +7,23 @@ enum class SaegilCharacter( val nickname: String, val gender: String, val personality: String, - val description: String + val description: String, + val comment: String, ) { SAEROM( R.drawable.img_saerom, "새롬", "여", "ENFP", - "동갑내기 친구" + "동갑내기 친구", + "동갑내기 친구와 “반말”로 편안하게 대화해보세요!" ), GILDONG( R.drawable.img_gildong, "길동", "남", "INTJ", - "연장자" + "연장자", + "연장자에게는 “존댓말”로 대화하는 걸 잊지마세요!" ) } \ No newline at end of file diff --git a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt index 8e35c28e..541ec8af 100644 --- a/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt +++ b/presentation/ai_conversation/src/main/java/com/saegil/ai_conversation/aiconversation/AiConversationScreen.kt @@ -52,6 +52,7 @@ fun AiConversationScreen( val state by viewModel.stateFlow.collectAsStateWithLifecycle() InternalAiConversationScreen( + character = character, state = state, modifier = modifier, onStopButtonClick = viewModel::stopChatSession, @@ -62,6 +63,7 @@ fun AiConversationScreen( @Composable internal fun InternalAiConversationScreen( + character: SaegilCharacter? = SaegilCharacter.SAEROM, state: AiConversationState, modifier: Modifier, onStopButtonClick: () -> Unit = {}, @@ -97,9 +99,11 @@ internal fun InternalAiConversationScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("전화 거는 중...", + Text( + "전화 거는 중...", style = MaterialTheme.typography.h3, - color = MaterialTheme.colorScheme.onBackground) + color = MaterialTheme.colorScheme.onBackground + ) Spacer(modifier = Modifier.height(18.dp)) Box( Modifier.border( @@ -108,8 +112,9 @@ internal fun InternalAiConversationScreen( shape = RoundedCornerShape(4.dp) // ← 코너를 4dp만큼 둥글게 ) ) { + Text( - "동갑내기 친구와 \"반말로\" 편안하게 대화해보세요!", + character?.comment ?: "", modifier = Modifier.padding(10.dp), style = MaterialTheme.typography.body2 ) @@ -118,17 +123,19 @@ internal fun InternalAiConversationScreen( Spacer(modifier = Modifier.height(60.dp)) Image( - painterResource(R.drawable.img_saerom), + painterResource(character!!.img), modifier = Modifier.size(200.dp), contentDescription = "새롬" ) + Text( - "새롬", + character.nickname, color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.h1, modifier = Modifier.padding(top = 18.dp) ) + Spacer(modifier = Modifier.height(120.dp)) Image( @@ -152,6 +159,7 @@ internal fun InternalAiConversationScreen( private fun AiConversationScreenPreview() { SaegilAndroidTheme { InternalAiConversationScreen( + character = SaegilCharacter.GILDONG, state = AiConversationState(), modifier = Modifier ) From 8af8278f2bbef7eeaef8f5e5d6c723f2b52c92da Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 03:09:54 +0900 Subject: [PATCH 11/15] fix: key --- .../main/java/com/saegil/data/remote/RealTimeServiceImpl.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index 37409bae..ccd7e339 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -42,8 +42,7 @@ class RealTimeServiceImpl( path = "/v1/realtime/sessions/$secret", request = { url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17&modalities=audio") -// header(HttpHeaders.Authorization, "Bearer ${BuildConfig.OPEN_AI_API_KEY}") - header(HttpHeaders.Authorization, "Bearer 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") + header(HttpHeaders.Authorization, "Bearer ${BuildConfig.}") header("OpenAI-Beta", "realtime=v1") } ) { From 9845e31148557bf3fe6e309f00ec92b063f9fb47 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 03:20:48 +0900 Subject: [PATCH 12/15] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=98=A4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 -- data/build.gradle.kts | 1 + data/src/main/java/com/saegil/data/di/NetworkModule.kt | 9 +++++---- .../java/com/saegil/data/remote/RealTimeServiceImpl.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5d483d0e..49ce842f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,8 +24,6 @@ android { versionName = "1.0.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - manifestPlaceholders["OPEN_AI_API_KEY"] = properties.getProperty("openai_api_key") - buildConfigField("String", "OPEN_AI_API_KEY", "\"${properties["openai_api_key"]}\"") manifestPlaceholders["NAVER_CLIENT_ID"] = properties.getProperty("naver_client_id") manifestPlaceholders["NATIVE_APP_KEY"] = properties.getProperty("kakao_native_app_key") buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["kakao_native_app_key"]}\"") diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 71be1a9c..b239eb05 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -22,6 +22,7 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") buildConfigField("String", "BASE_URL", "\"${properties.getProperty("base_url")}\"") + buildConfigField("String", "OPEN_AI_API_KEY", "\"${properties.getProperty("openai_api_key")}\"") } buildTypes { diff --git a/data/src/main/java/com/saegil/data/di/NetworkModule.kt b/data/src/main/java/com/saegil/data/di/NetworkModule.kt index d15dc7ab..7aa13a5e 100644 --- a/data/src/main/java/com/saegil/data/di/NetworkModule.kt +++ b/data/src/main/java/com/saegil/data/di/NetworkModule.kt @@ -8,6 +8,8 @@ import com.saegil.data.remote.FeedService import com.saegil.data.remote.FeedServiceImpl import com.saegil.data.remote.HttpRoutes.ASSISTANT import com.saegil.data.remote.HttpRoutes.GET_REALTIME_TOKEN +import com.saegil.data.remote.HttpRoutes.NEWS +import com.saegil.data.remote.HttpRoutes.NEWS_INTERESTS import com.saegil.data.remote.HttpRoutes.OAUTH_LOGOUT import com.saegil.data.remote.HttpRoutes.OAUTH_VALIDATE_TOKEN import com.saegil.data.remote.HttpRoutes.OAUTH_WITHDRAWAL @@ -84,10 +86,9 @@ object NetworkModule { SIMULATION_LOG, TTS, USER, - ASSISTANT, GET_REALTIME_TOKEN - GET_REALTIME_TOKEN, - - NEWS_INTERESTS, + ASSISTANT, + GET_REALTIME_TOKEN, + NEWS_INTERESTS, NEWS ).any { it in path } } diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index ccd7e339..c3ae94db 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -42,7 +42,7 @@ class RealTimeServiceImpl( path = "/v1/realtime/sessions/$secret", request = { url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17&modalities=audio") - header(HttpHeaders.Authorization, "Bearer ${BuildConfig.}") +// header(HttpHeaders.Authorization, "Bearer ${BuildConfig.}") header("OpenAI-Beta", "realtime=v1") } ) { From 3a49ef0eb37f07be54327c773bc857bfb6831397 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 03:23:06 +0900 Subject: [PATCH 13/15] chore: build config --- .../src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt index c3ae94db..c81cd4c8 100644 --- a/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt +++ b/data/src/main/java/com/saegil/data/remote/RealTimeServiceImpl.kt @@ -42,7 +42,7 @@ class RealTimeServiceImpl( path = "/v1/realtime/sessions/$secret", request = { url("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17&modalities=audio") -// header(HttpHeaders.Authorization, "Bearer ${BuildConfig.}") + header(HttpHeaders.Authorization, "Bearer ${BuildConfig.OPEN_AI_API_KEY}") header("OpenAI-Beta", "realtime=v1") } ) { From fb16d313bad8e9d42be7955ca4e307032be77a31 Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 03:26:03 +0900 Subject: [PATCH 14/15] chore: onboarding comment --- .../main/java/com/saegil/android/navigation/NavGraph.kt | 2 +- .../com/saegil/onboarding/component/OnboardingPage.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt index 9a8d0c99..629a2468 100644 --- a/app/src/main/java/com/saegil/android/navigation/NavGraph.kt +++ b/app/src/main/java/com/saegil/android/navigation/NavGraph.kt @@ -141,7 +141,7 @@ fun NavGraph(navController: NavHostController, modifier: Modifier) { composable(Screen.Onboarding.route) { OnboardingScreen( navigateToMain = { - navController.navigate(Screen.Learning.route) { + navController.navigate(Screen.AiConversation.route) { popUpTo(Screen.Splash.route) { inclusive = true } } } diff --git a/presentation/onboarding/src/main/java/com/saegil/onboarding/component/OnboardingPage.kt b/presentation/onboarding/src/main/java/com/saegil/onboarding/component/OnboardingPage.kt index 040b9a87..f0f793a2 100644 --- a/presentation/onboarding/src/main/java/com/saegil/onboarding/component/OnboardingPage.kt +++ b/presentation/onboarding/src/main/java/com/saegil/onboarding/component/OnboardingPage.kt @@ -6,14 +6,14 @@ sealed class OnboardingPage( val showButton: Boolean = false ) { data object OnboardingFirst : - OnboardingPage("언제 어디서나 대화 연습", "AI 시뮬레이션 대화를 통해 일상에서 자주\n일어나는 상황 속 대화를 연습할 수 있어요.") + OnboardingPage("언제 어디서나 대화 연습", "AI 전화 회화를 통해 일상에서 자주\n일어나는 상황 속 대화를 연습할 수 있어요.") data object OnboardingSecond : - OnboardingPage("공지사항을 한 곳에서 확인", "여기저기 흩어진 있던 북한이탈주민 대상\n공지사항들을 편하게 확인할 수 있어요.") + OnboardingPage("공지사항을 놓치지 않게", "푸시알림을 통해 정착에 도움이 되는\n공지사항을 제 때 확인할 수 있어요.") data object OnboardingThird : OnboardingPage( - "한 눈에 보는 근처 복지시설", - "현재 있는 장소 근처에 있는 복지시설의\n위치와 기관 정보를 쉽게 찾아볼 수 있어요.", + "관심사에 맞는 뉴스로 학습", + "관심사에 따라 하루 5개의 뉴스, OX 퀴즈로\n독해력과 사회 이해도를 향상시킬 수 있어요.", showButton = true ) From ed04ebfcf6481bb23626edd73d89f595d4b1fa3d Mon Sep 17 00:00:00 2001 From: Yu Jin Date: Tue, 15 Jul 2025 16:56:29 +0900 Subject: [PATCH 15/15] =?UTF-8?q?chore:=20news=20UI=20=EA=B9=A8=EC=A7=80?= =?UTF-8?q?=EB=8A=94=EA=B1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/saegil/news/news/NewsScreen.kt | 17 +++++++++++++---- .../com/saegil/news/newsquiz/NewsQuizScreen.kt | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/presentation/news/src/main/java/com/saegil/news/news/NewsScreen.kt b/presentation/news/src/main/java/com/saegil/news/news/NewsScreen.kt index fe089f40..9e826b2b 100644 --- a/presentation/news/src/main/java/com/saegil/news/news/NewsScreen.kt +++ b/presentation/news/src/main/java/com/saegil/news/news/NewsScreen.kt @@ -3,6 +3,7 @@ package com.saegil.news.news import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues @@ -17,7 +18,9 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Checkbox @@ -103,10 +106,15 @@ internal fun NewsScreen( private fun LoadingState( modifier: Modifier = Modifier, ) { - SaegilLoadingWheel( + Box( modifier = modifier - .wrapContentSize() - ) + .fillMaxSize(), // 화면 전체를 채움 + contentAlignment = Alignment.Center // 가운데 정렬 + ) { + SaegilLoadingWheel( + modifier = Modifier.wrapContentSize() + ) + } } @Composable @@ -138,7 +146,8 @@ private fun NoTopicsState( Column( modifier = modifier .fillMaxSize() - .padding(horizontal = 35.dp), + .verticalScroll(rememberScrollState()) + .padding(horizontal = 20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(24.dp)) diff --git a/presentation/news/src/main/java/com/saegil/news/newsquiz/NewsQuizScreen.kt b/presentation/news/src/main/java/com/saegil/news/newsquiz/NewsQuizScreen.kt index d6e532c4..0391d62d 100644 --- a/presentation/news/src/main/java/com/saegil/news/newsquiz/NewsQuizScreen.kt +++ b/presentation/news/src/main/java/com/saegil/news/newsquiz/NewsQuizScreen.kt @@ -12,7 +12,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme @@ -84,10 +86,15 @@ internal fun NewsQuizScreen( private fun LoadingState( modifier: Modifier = Modifier, ) { - SaegilLoadingWheel( + Box( modifier = modifier - .wrapContentSize() - ) + .fillMaxSize(), // 화면 전체를 채움 + contentAlignment = Alignment.Center // 가운데 정렬 + ) { + SaegilLoadingWheel( + modifier = Modifier.wrapContentSize() + ) + } } @Composable @@ -100,7 +107,8 @@ internal fun QuizContent( Column( modifier = modifier - .fillMaxSize(), + .verticalScroll(rememberScrollState()) + .fillMaxSize(), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Column(