From c6826f47bf71a9c4b6be45bfe620948d3e2ee9d9 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Thu, 18 Sep 2025 22:07:23 +0900 Subject: [PATCH 01/22] =?UTF-8?q?[refactor]:=20conflict=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commonMain/kotlin/org/whosin/client/App.kt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt index 0d6240b..c94c539 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt @@ -1,31 +1,25 @@ package org.whosin.client import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController import org.jetbrains.compose.ui.tooling.preview.Preview import org.whosin.client.core.navigation.WhosInNavGraph -import ui.theme.WhosInTheme -import whosinclient.composeapp.generated.resources.Res -import whosinclient.composeapp.generated.resources.compose_multiplatform - @Composable @Preview fun App() { - WhosInTheme { + MaterialTheme { val navController = rememberNavController() WhosInNavGraph( modifier = Modifier - .statusBarsPadding() + .safeContentPadding() .fillMaxSize(), navController = navController ) - // Test용으로 남겨둔 코드, 추후 삭제 예정 - // 확인하려면 위의 코드는 주석처리하고 실행 -// DummyScreen() } } \ No newline at end of file From f5d1cd6aef68b745d718a9add8278d62fb9ab000 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 12:41:41 +0900 Subject: [PATCH 02/22] =?UTF-8?q?[feat]:=20Route=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=20=EB=B0=8F=20App.kt=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/org/whosin/client/App.kt | 12 +++++++---- .../whosin/client/core/navigation/Route.kt | 21 +++++++++++++++++++ .../kotlin/org/whosin/client/di/DIModules.kt | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt index c94c539..6d4249c 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt @@ -1,25 +1,29 @@ package org.whosin.client import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController import org.jetbrains.compose.ui.tooling.preview.Preview import org.whosin.client.core.navigation.WhosInNavGraph +import ui.theme.WhosInTheme + @Composable @Preview fun App() { - MaterialTheme { + WhosInTheme { val navController = rememberNavController() WhosInNavGraph( modifier = Modifier - .safeContentPadding() + .statusBarsPadding() .fillMaxSize(), navController = navController ) + // Test용으로 남겨둔 코드, 추후 삭제 예정 + // 확인하려면 위의 코드는 주석처리하고 실행 +// DummyScreen() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt index 0a716ff..d530d65 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt @@ -7,9 +7,30 @@ sealed interface Route { @Serializable data object AuthGraph: Route + @Serializable + data object Splash: Route + @Serializable data object Login: Route + @Serializable + data object FindPassword: Route + + @Serializable + data object Signup: Route + + @Serializable + data object EmailVerification: Route + + @Serializable + data object PasswordInput: Route + + @Serializable + data object NicknameInput: Route + + @Serializable + data object ClubCodeInput: Route + /* 메인 화면 */ @Serializable data object Home: Route diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt index 660d86e..ed87075 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt @@ -11,7 +11,7 @@ import org.whosin.client.data.repository.DummyRepository import org.whosin.client.data.repository.ClubRepository import org.whosin.client.data.repository.MemberRepository import org.whosin.client.presentation.dummy.DummyViewModel -import org.whosin.client.presentation.auth.LoginViewModel +import org.whosin.client.presentation.auth.login.viewmodel.LoginViewModel import org.whosin.client.presentation.home.HomeViewModel import org.whosin.client.presentation.mypage.MyPageViewModel From 679a667d1aacfe2313460aba2cbd9fe095893354 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 12:42:23 +0900 Subject: [PATCH 03/22] =?UTF-8?q?[ui]:=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94=EA=B0=80=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composeResources/drawable/ic_back.png | Bin 0 -> 282 bytes .../drawable/img_logo_orange.png | Bin 0 -> 2716 bytes .../composeResources/drawable/img_logo_white.png | Bin 0 -> 2253 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_back.png create mode 100644 composeApp/src/commonMain/composeResources/drawable/img_logo_orange.png create mode 100644 composeApp/src/commonMain/composeResources/drawable/img_logo_white.png diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_back.png b/composeApp/src/commonMain/composeResources/drawable/ic_back.png new file mode 100644 index 0000000000000000000000000000000000000000..bc7358637f3620c89dc4bf30b1aabe81677c3f0b GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^+(0bC!3HFwb&h8MDb50q$YP+lFbFd;%$g$s6l5$8 za(7}_cTVOdki(Mh=R!hFb;WASm%)fDBv3Ul?TVRBzNn>kXXM9y3fxsoeK%goWF zVj?SK?)&^~RIb(f`u_a=3%)J z*Tr7Hc@oeN3)gS};1T+-KtRzmv6B)M?r_-*X#6C(aROjpQ)^QIXnDl*%ZCF1xDl2p zQ>Qziof*^Hv~h6=gC3U`sN{bQMpH7#)%*MPXck0xvLcM`@oWeO6I(>P>3U|1(U5~A zT%ho)7W7NBws*cR88?H4P2$*wZ{Mm}x%qE1dK@(MjHK!JwyuN|vZ0bhj+0^NOcx8@p%fiyZ---_hB+@ zE+(0ze$M8K1$CJL&l~IodSA>>HoqaxKMgYvKtmAYf(iUj>b4AR2fYi=9ZIKXIXw2R z7Jlv=JzLiQH;MqwQ|x!xI>7owWh>a!o)%0kro-~qfive)3|(J}P7BJ;g7K1>VimJN zzq2e7f-eeQ9qWfQ%eSJ{_MTg~2aVr_s=*0Y>i7t`q43-Hzd(X`m8k|6CCkpac56k?i7A~{Z?P3 z0)Pg;lb1{AwS8mnIU;@ARi!iARXJ{FgejqEkR8?c!Ascs5w%CT$&}3!_^USY-_@z8 z8QutPdDGPNam}*4?=qj{K$0f)Mjs!#X2nd;JJ1_eUj4*K!&CUpxc=AHcwKbnqI>a_ zvHa4)j;5ZU=pH;PCQ+Z}=E7W}j`{bDZI?+f#ymNX|CHq!opNAvPsl%l`_nX(roAj_yH3=Hw4dAaD!#QeF%ZTB@wf0+4#Tjx&L zjm%W2*-we~T44`kgjz~D8}}fo<71|ZKl412^=7&o`JQ%or4S)|}>PQZin`-^7mY9KP|wulskMo6o)!&T~sk zFlh08S?}^&_JZS;GJIXe25D~ov}Gs8DckQ4q!1zSxuTF^>1uV{_M2Ib!F}t z&D!4VQqb^vJA|xPOSrklL|R1b8}*;`%*1e*wYrU=UYT;x$X z;YTrH&wga0x{<}kTlz~oqy03quP-^R;zAtWCm#9`Q=7pr#4kF467k4 zr9-U#;h5Zt;Mv(w;Aq(SLRsEbjRDGMm5J9^W>}sx1xm81#8VO+`7`PML;|!>#a}+W zpZj9<2eh)BIB3~zh`KMR;jT(q^+An6VgtoQHkn-d_ogdy(9O(3NQp!em{Rn> zC^Nl`SD6$h&-^~5nNKl@&i9R@3S?8e1~=z(_hX&?(>}JP7x+K8dw-fm-Z89x9#NFh zIaKAltP)++p&;gX+%xia_;67#bNaJVpJaa${wgHjd)bc^(`RSI?Xn?=x@FT$wFfM# zO80IUrgL5E&Xh7~c;-pc7Czf`G1O@rt8Vg#39XeLjx4F#9WiG6#x*bTXq-B{`Q1b*UeBd^h>WZT^1+?g zamQxG4v_7)Dcg+Q_olw!$PG7s)CKt^<3tFIh3Lu9)FQToGWV7!=892RJ{yjFTzk1Q zWLap(R%BpK#;{_zxG6b^Xl}-gC2PQ6-bMUMOTtK#W#v$@G3pUQ0m=z+!BXcto4X9Q zrQ}`a%GP)8oJP|K9lauVj516v8ca+QFYls*@INA9!d`PdQd=u)%3_WU0s_j(=j)D- z(7Xy~{t8G=fDLWXh$^eU!UE4oCvlhp0jb|qWl56(m$VWV^_@PD%l{ZdVp;u?sAX(w zp@AbrYc5f=|HmcHnd>~X3b`L01^P6WO|vI82XlF8ay{T@yVBcKkB{cOdh&Ywx`?h&&WpQ`dJv9hx|X5HuJZaH z9Hbql03YYpeEHi-(6wr!#Qt2x7p;4R7)1JqQ`UfAJH&B$LIQI-9#fKWNaw*)FjOjOU1sSd4?xs}M)uaQ2=h z$U}iy+@omc8X~SRFJ?@bB)l|lQx;0^SoNSG`910)kh%cFJHlqW!%AVYn{D)cEb(g& zC9u7yjhS{V`eIbhdv)mx RNmgY97Ml)~xe*h{7?r8u3 literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/composeResources/drawable/img_logo_white.png b/composeApp/src/commonMain/composeResources/drawable/img_logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bac455f24783f9b6d3b5d7f260ea3c0a78184388 GIT binary patch literal 2253 zcmbtWX*3iJ7oIVRXol}g4PualvSp28tRWIpO2}UJC1!}h*j}RSvc=5w+9|SR*ND$D zma!XSmt{~G#y*zUd%mCFIq#qEp7Y%2-241_?)`DkjW#yY1+($80RRB7zMeMXjPYj( zU_F19@3@#boY4g@z2`mv0Q;rC0R&`bai2ATJ_ub+Kq-lD<*Yd8sPRw(04R@VKeA;8 z09ftxwKbl+0j^EK-GohHEVPX~&#UXybMx4klb1A^mCK(*AtBGQksYr}h6XIFL%pNp z)Qm8G5+d@qHJPGs00Z2v7XlLV%!|{*kWL^ApgxR%qbl>AXX66PW&PmFqoGWE)$Mt5Ep9Tf4>DRw-`|95z)B86rueis3&Cl>F1 z)@_aTAd8&JBQF!%(L)sF<>zUU-PC50-f%<_e!(GUt+Vyl*GmmpbD536TsOSK1AcW1 z%_2Q6wmMFxSS-UoCBfbLWH-V*L-VaNKxXwYd!7&9p@u`T!0V4M3TD^=h+H7Od~S8wHb9d2`2@q;Ar3MB zm2T20G8uK&u(@qK(9*hja?5!$5 z;GEkP|8e4fSjPV)b@4R|_YdFl0l+~In`BwOLNWvLz&2bu_3Xjy@*`c-7mD;LQB64Y zpbv+Puy0q8q!lIOndB|cF=tAtR@Rz(MnR5q!PRL>5E_wX9v|85$sr;qH) z&GrRswc=mU#n&Tgg#8ZgwxaF|3)URSO@S|;az7i?QC$n8q~3$lijR*fc%^W>PQIPz ztx-e|3$26Lt5Zr%b9B0U?SOo4BjnlzP19Dvmt-(xQET)mMF&4Q>5rD(m0mMId-6sG zz5SnXs@6Uxjc0d;9i#revKWtEi-5yD%jL?p`-1omT%*EQjwL{;7~8sFl?W@GiP<86 zMEcV@IHzx8`y2SZ-t^-~y*aDhg1>^M3Cl@xSk%GFFHM+LwUJa^Y3@9}?+`hB*ytyG zY$blTemy+_i^7SOun$R>1QjEEEphJxn4F1Q4N|XD7AHTjfJq;N4^}c=%N?_e#f&6g zYCXtl?t?3QW=h{V95suvLYK#QWg z6C>=|+ur3lu}Vr8OQ(#WeoP1(mVD}e7jnW?7>g908r{|?-Z5GT_8*31F*Y0MWElU? zZQ8A@NsIt_bf$=i#w7VqX>B5(ois_5L?@NA&iclY?n~%MKm05tNI;kDFs6vm(=|40 zGGk%qaS;#ID$w3wsT@jcq1$fbyz20AF(f*qg+f%B%LNtAl?dU!F!`d7PmK`_VyW*K z!|P~`QP@Zuw?hVL_$8(TrY@a36@8*Mj5>D>6W;^OdUExv_)GWqG-j$R!G$oB4Jka3 z>Jgqt0;f>H+jxmV1^fwg^1hi$U+UuiD5t5VYWDP-=N^dRRkqp7P79@A3*93A)z09n z)d{Mu?r?>~i}PF$$qf>~MjQkWX&IGK-h1!bp3qRiolKYSvM;+8g1B+Jdc=ff!vWr1 z<@^+v!2qScg(*&`b4#|qdWM!T1Eo72>AH4}!O5RO@9n8$$H&XO?L~%=0|Kcw_!uw# z!W)Ph4lKUr2&^>$bhAT__s1X883=Q36Vk4AAhCQvl(S`rS=&HqZlSP4@Pnh({q0(P zK$!}7hH?y12$|oVA=ta2YqV9+hdTXJX${ddm}q&4RBZqRg8RCNc~ZV<4BC!y>e@? zgv%7Ov}sX!T+VmFPQuc55B$>O+U9TpFeO-M8vDJGSHNdonRv2EwgqQXgRf}*NIBMS*;E}VyG!%-6#{;IX==p#jG9zzZaNkRmS zZ%b1lv)q$5uk@1{j_;xJ{GRyTR8<+T;?0+zCBmyd1&}l}VlM)(_qB@!S1CRBnDI>_ zJeh@59P?-D1#G7IW(AIUh70kLYD8C8f&zfs>r97jn9UGWKSUEN&D>Kl`68`6ZI!H5 x0N3l)!fn(FL310_+?#=z8D1tY5ogc|#B|fMKfe~Wd-i<*^mUB1OEqo6{sQXsUiJV0 literal 0 HcmV?d00001 From 8a6aa1b56dd18aea30d2cd35bcb5ecbfeb81534f Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 13:49:03 +0900 Subject: [PATCH 04/22] =?UTF-8?q?[ui]:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composeApp/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 6df0e3f..e5932c2 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -57,11 +57,13 @@ kotlin { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) + implementation(compose.material) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.runtimeCompose) + implementation(libs.androidx.material.icons.extended) // Navigation implementation(libs.navigation.compose) // Ktor 핵심 클라이언트 @@ -120,6 +122,7 @@ android { } dependencies { + implementation(libs.androidx.material.icons.extended) debugImplementation(compose.uiTooling) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1fd109..32d753f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ navigation-compose = "2.9.0-beta03" ktor = "3.2.3" kotlinx-serialization = "1.9.0" koin = "4.1.0" +materialIconsExtended = "1.7.8" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -49,6 +50,7 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" } +androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "materialIconsExtended" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 074c522fdbba32bc6e4f3d273b971c3a08660860 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 13:53:43 +0900 Subject: [PATCH 05/22] =?UTF-8?q?[ui]:=20=EA=B3=B5=ED=86=B5=20InputField?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../login/component/CommonLoginInputField.kt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt new file mode 100644 index 0000000..a495387 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt @@ -0,0 +1,108 @@ +package org.whosin.client.presentation.auth.login.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun CommonLoginInputField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + isPassword: Boolean = false, + maxLength: Int? = null +) { + var passwordVisible by remember { mutableStateOf(false) } + val isMaxLengthReached = maxLength != null && value.length >= maxLength + OutlinedTextField( + value = value, + onValueChange = onValueChange, + placeholder = { + Text( + text = placeholder, + color = Color(0xFFB2B2B2), + fontSize = 16.sp, + fontWeight = FontWeight.W500 + ) + }, + textStyle = TextStyle( + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.W500 + ), + modifier = modifier + .height(54.dp) + .fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = if (isMaxLengthReached) Color(0xFFE5E5E5) else Color(0xFFF89531), + unfocusedBorderColor = Color(0xFFE5E5E5), + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + cursorColor = Color(0xFFB2B2B2) + ), + visualTransformation = if (isPassword && !passwordVisible) PasswordVisualTransformation() else VisualTransformation.None, + trailingIcon = if (isPassword) { + { + IconButton( + onClick = { passwordVisible = !passwordVisible } + ) { + Icon( + imageVector = if (passwordVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility, + contentDescription = if (passwordVisible) "비밀번호 숨기기" else "비밀번호 보기", + tint = Color(0xFFB2B2B2), + modifier = Modifier.size(20.dp) + ) + } + } + } else null, + singleLine = true + ) +} + +@Preview() +@Composable +fun CommonLoginInputFieldPreview() { + var text by remember { mutableStateOf("") } + CommonLoginInputField( + value = text, + onValueChange = { text = it }, + placeholder = "이메일을 입력해주세요" + ) +} + +@Preview +@Composable +fun CommonLoginInputFieldPasswordPreview() { + var password by remember { mutableStateOf("password123") } + CommonLoginInputField( + value = password, + onValueChange = { password = it }, + placeholder = "비밀번호를 입력해주세요", + isPassword = true + ) +} From e2883167f8f7a1b56e510e4644a1dea7cc69a241 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 13:54:07 +0900 Subject: [PATCH 06/22] =?UTF-8?q?[ui]:=20=EA=B3=B5=ED=86=B5=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/component/CommonLoginButton.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt new file mode 100644 index 0000000..9830fb0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt @@ -0,0 +1,69 @@ +package org.whosin.client.presentation.auth.login.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +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.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview +import ui.theme.WhosInTheme + +@Composable +fun CommonLoginButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + backgroundColor: Color = Color(0xFFF89531), // 오렌지색 + textColor: Color = Color.White +) { + Button( + onClick = onClick, + modifier = modifier + .fillMaxWidth() + .height(54.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = backgroundColor, + contentColor = textColor, + disabledContainerColor = Color(0xFFD2D2D2), + disabledContentColor = Color.White + ), + enabled = enabled + ) { + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.W600 + ) + } +} + +@Preview +@Composable +fun CommonLoginButtonPreview() { + WhosInTheme { + CommonLoginButton( + text = "로그인", + onClick = {} + ) + } +} + + +@Preview +@Composable +fun CommonLoginButtonDisabledPreview() { + CommonLoginButton( + text = "비활성화된 버튼", + onClick = {}, + enabled = false + ) +} \ No newline at end of file From 505a00a8cf31de00207feb3a0b68dad295717b4f Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 13:55:09 +0900 Subject: [PATCH 07/22] =?UTF-8?q?[ui]:=20=EA=B3=B5=ED=86=B5=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=ED=95=84=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/component/NumberInputBox.kt | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt new file mode 100644 index 0000000..105a791 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt @@ -0,0 +1,111 @@ +package org.whosin.client.presentation.auth.login.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun NumberInputBox( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + onBackspace: (() -> Unit)? = null, + onFocusChanged: ((Boolean) -> Unit)? = null, + containerColor: Color = Color.White, + borderColor: Color = Color(0xFFE5E5E5), + focusedBorderColor: Color = Color(0xFFF89531), + isFocused: Boolean = false +) { + + // 커서 위치 제어용 + val textFieldValue = remember(value) { + val displayText = value.ifEmpty { "\u200B" } + TextFieldValue( + text = displayText, + selection = TextRange(displayText.length) + ) + } + + val textStyle = TextStyle( + color = Color.Black, + fontSize = 24.sp, + fontWeight = FontWeight.W600, + textAlign = TextAlign.Center + ) + + val currentBorderColor = if (isFocused) focusedBorderColor else borderColor + + Box( + modifier = modifier + .size(width = 50.dp, height = 54.dp) + .background(containerColor, RoundedCornerShape(8.dp)) + .border(1.dp, currentBorderColor, RoundedCornerShape(8.dp)), + contentAlignment = Alignment.Center + ) { + BasicTextField( + value = textFieldValue, + onValueChange = { newValue -> + val cleaned = newValue.text.replace("\u200B", "") + val filtered = cleaned.filter { it.isDigit() }.take(1) + + if (newValue.text.isEmpty() && value.isEmpty()) { + onBackspace?.invoke() + return@BasicTextField + } + + onValueChange(filtered) + }, + textStyle = textStyle.copy( + color = if (value.isEmpty()) Color.Transparent else Color.Black + ), + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + cursorBrush = SolidColor(Color(0xFFB2B2B2)), + modifier = Modifier.onFocusChanged { focusState -> + onFocusChanged?.invoke(focusState.isFocused) + }, + decorationBox = { innerTextField -> + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + innerTextField() + } + } + ) + } +} + +@Preview +@Composable +fun NumberInputBoxPreview() { + var digit by remember { mutableStateOf("") } + NumberInputBox( + value = digit, + onValueChange = { digit = it } + ) +} \ No newline at end of file From 0cbdd4283a036b0e9120b4cdecb334b3f7ab00e8 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:10:02 +0900 Subject: [PATCH 08/22] =?UTF-8?q?[ui]:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/LoginScreen.kt | 43 ----- .../presentation/auth/login/LoginScreen.kt | 161 ++++++++++++++++++ .../login/component/CommonLoginInputField.kt | 2 +- .../{ => login/viewmodel}/LoginViewModel.kt | 2 +- 4 files changed, 163 insertions(+), 45 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt rename composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/{ => login/viewmodel}/LoginViewModel.kt (95%) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt deleted file mode 100644 index cb21cf2..0000000 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.whosin.client.presentation.auth - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import org.jetbrains.compose.ui.tooling.preview.Preview - -@Composable -fun LoginScreen( - modifier: Modifier = Modifier, - onNavigateToHome: () -> Unit -) { - Box( - modifier = modifier - ) { - Column( - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = "Login Screen" - ) - Button( - onClick = onNavigateToHome - ){ - Text(text = "Go to Home") - } - } - } -} - -@Preview -@Composable -fun LoginScreenPreview() { - LoginScreen( - modifier = Modifier.fillMaxSize(), - onNavigateToHome = {} - ) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt new file mode 100644 index 0000000..871d8ed --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt @@ -0,0 +1,161 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.CommonLoginInputField +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.email_label +import whosinclient.composeapp.generated.resources.email_placeholder +import whosinclient.composeapp.generated.resources.find_password_button +import whosinclient.composeapp.generated.resources.img_logo_orange +import whosinclient.composeapp.generated.resources.login_button +import whosinclient.composeapp.generated.resources.login_title +import whosinclient.composeapp.generated.resources.password_label +import whosinclient.composeapp.generated.resources.password_placeholder +import whosinclient.composeapp.generated.resources.signup_button + +@Composable +fun LoginScreen( + modifier: Modifier = Modifier, + onNavigateToHome: () -> Unit, + onNavigateToFindPassword: () -> Unit = {}, + onNavigateToSignup: () -> Unit = {} +) { + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 96.dp, start = 16.dp, end = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(Res.drawable.img_logo_orange), + contentDescription = "logo", + modifier = Modifier.size(width = 160.dp, height = 122.dp) + ) + Text( + text = stringResource(Res.string.login_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + modifier = Modifier.padding(top = 16.dp, bottom = 32.dp) + ) + + + Text( + text = stringResource(Res.string.email_label), + fontWeight = FontWeight.W500, + fontSize = 16.sp, + modifier = Modifier + .align(Alignment.Start) + .padding(bottom = 10.dp) + ) + CommonLoginInputField( + value = email, + onValueChange = { email = it }, + placeholder = stringResource(Res.string.email_placeholder), + modifier = Modifier.padding(bottom = 10.dp) + ) + + Text( + text = stringResource(Res.string.password_label), + fontWeight = FontWeight.W500, + fontSize = 16.sp, + modifier = Modifier + .align(Alignment.Start) + .padding(bottom = 10.dp) + ) + CommonLoginInputField( + value = password, + onValueChange = { password = it }, + placeholder = stringResource(Res.string.password_placeholder), + isPassword = true, + modifier = Modifier.padding(bottom = 16.dp) + ) + + CommonLoginButton( + text = stringResource(Res.string.login_button), + onClick = onNavigateToHome, + modifier = Modifier.padding(bottom = 12.dp) + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + onClick = onNavigateToSignup, + modifier = Modifier.padding(horizontal = 4.dp), + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Black + ) + ) { + Text( + text = stringResource(Res.string.signup_button), + fontWeight = FontWeight.W500, + fontSize = 16.sp + ) + } + + TextButton( + onClick = onNavigateToFindPassword, + modifier = Modifier.padding(horizontal = 4.dp), + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Black + ) + ) { + Text( + text = stringResource(Res.string.find_password_button), + fontWeight = FontWeight.W500, + fontSize = 16.sp + ) + } + } + } + } +} + +@Preview +@Composable +fun LoginScreenPreview() { + LoginScreen( + modifier = Modifier, + onNavigateToHome = {}, + onNavigateToFindPassword = {}, + onNavigateToSignup = {} + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt index a495387..94b9e5b 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt @@ -66,7 +66,7 @@ fun CommonLoginInputField( cursorColor = Color(0xFFB2B2B2) ), visualTransformation = if (isPassword && !passwordVisible) PasswordVisualTransformation() else VisualTransformation.None, - trailingIcon = if (isPassword) { + trailingIcon = if (isPassword && value.isNotEmpty()) { { IconButton( onClick = { passwordVisible = !passwordVisible } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginViewModel.kt rename to composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt index afbad3b..7517f45 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt @@ -1,4 +1,4 @@ -package org.whosin.client.presentation.auth +package org.whosin.client.presentation.auth.login.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope From a5695a7841640919f4ce95680464e0c37ddd1104 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:10:58 +0900 Subject: [PATCH 09/22] =?UTF-8?q?[ui]:=20=EC=8A=A4=ED=94=8C=EB=9E=98?= =?UTF-8?q?=EC=8B=9C=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/login/SplashScreen.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt new file mode 100644 index 0000000..616f400 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt @@ -0,0 +1,49 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.img_logo_white + +@Composable +fun SplashScreen( + modifier: Modifier = Modifier, + onNavigateToLogin: () -> Unit = {} +) { + + LaunchedEffect(Unit) { + delay(2000) + onNavigateToLogin() + } + + Box( + modifier = modifier + .fillMaxSize() + .background(Color(0xFFF89531)), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(Res.drawable.img_logo_white), + contentDescription = "logo", + modifier = Modifier.size(width = 160.dp, height = 122.dp) + ) + } +} + +@Preview +@Composable +fun SplashScreenPreview() { + SplashScreen() +} \ No newline at end of file From a80d7ff031dc35dfe3e44458da4723cd20e0e3a6 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:12:29 +0900 Subject: [PATCH 10/22] =?UTF-8?q?[ui]:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/SignupEmailInputScreen.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt new file mode 100644 index 0000000..436b38a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt @@ -0,0 +1,109 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.CommonLoginInputField +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.email_placeholder +import whosinclient.composeapp.generated.resources.ic_back +import whosinclient.composeapp.generated.resources.next_button +import whosinclient.composeapp.generated.resources.signup_title + +@Composable +fun SignupScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onNavigateToEmailVerification: (String) -> Unit = {} +) { + var email by remember { mutableStateOf("") } + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier + .size(18.dp) + ) + } + + Text( + text = stringResource(Res.string.signup_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + CommonLoginInputField( + value = email, + onValueChange = { email = it }, + placeholder = stringResource(Res.string.email_placeholder) + ) + } + + CommonLoginButton( + text = stringResource(Res.string.next_button), + onClick = { onNavigateToEmailVerification(email) }, + enabled = email.isNotBlank(), + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + + +@Preview +@Composable +fun EmailInputScreenPreview() { + SignupScreen( + modifier = Modifier, + onNavigateBack = {}, + onNavigateToEmailVerification = { email -> + // 이메일 처리 로직 + } + ) +} \ No newline at end of file From 19f2c46201a46decc22492036e7ef8f56445b41c Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:12:59 +0900 Subject: [PATCH 11/22] =?UTF-8?q?[ui]:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=EC=8B=9C=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/FindPasswordScreen.kt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt new file mode 100644 index 0000000..8c54388 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt @@ -0,0 +1,108 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.CommonLoginInputField +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.email_placeholder +import whosinclient.composeapp.generated.resources.ic_back +import whosinclient.composeapp.generated.resources.password_reset_title +import whosinclient.composeapp.generated.resources.send_email_button + +@Composable +fun FindPasswordScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onPasswordResetComplete: (String) -> Unit = {} +) { + var email by remember { mutableStateOf("") } + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier + .size(18.dp) + ) + } + + Text( + text = stringResource(Res.string.password_reset_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + CommonLoginInputField( + value = email, + onValueChange = { email = it }, + placeholder = stringResource(Res.string.email_placeholder) + ) + } + + CommonLoginButton( + text = stringResource(Res.string.send_email_button), + onClick = { onPasswordResetComplete(email) }, + enabled = email.isNotBlank(), + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + +@Preview +@Composable +fun PasswordResetScreenPreview() { + FindPasswordScreen( + modifier = Modifier, + onNavigateBack = {}, + onPasswordResetComplete = { email -> + // 이메일 처리 로직 + } + ) +} \ No newline at end of file From b72d0f02a6f7e17b44ede7851adc1f9a35410637 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:13:57 +0900 Subject: [PATCH 12/22] =?UTF-8?q?[ui]:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=B2=88=ED=98=B8=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/EmailVerificationScreen.kt | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt new file mode 100644 index 0000000..3c73be9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt @@ -0,0 +1,174 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.background +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.NumberInputBox +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.confirm_button +import whosinclient.composeapp.generated.resources.email_verification_title +import whosinclient.composeapp.generated.resources.ic_back + +@Composable +fun EmailVerificationScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onVerificationComplete: (String) -> Unit = {} +) { + var verificationCode by remember { mutableStateOf(arrayOf("", "", "", "", "", "")) } + var currentFocusIndex by remember { mutableStateOf(0) } + val focusRequesters = remember { List(6) { FocusRequester() } } + val keyboardController = LocalSoftwareKeyboardController.current + + // 화면 진입 시 첫 번째 입력 박스에 포커스 + LaunchedEffect(Unit) { + delay(300) + focusRequesters[0].requestFocus() + keyboardController?.show() + } + + // 화면 종료 시 키보드 숨기기 + DisposableEffect(Unit) { + onDispose { + keyboardController?.hide() + } + } + + val fullCode = verificationCode.joinToString("") + val isComplete = fullCode.length == 6 + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier.size(18.dp) + ) + } + + Text( + text = stringResource(Res.string.email_verification_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + // 6자리 인증번호 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + repeat(6) { index -> + NumberInputBox( + value = verificationCode[index], + onValueChange = { input -> + if (input.length <= 1 && input.all { it.isDigit() }) { + val newCode = verificationCode.copyOf() + val wasEmpty = verificationCode[index].isEmpty() + newCode[index] = input + verificationCode = newCode + + if (input.isNotEmpty() && index < 5) { + currentFocusIndex = index + 1 + focusRequesters[index + 1].requestFocus() + } else if (input.isEmpty() && !wasEmpty && index > 0) { + currentFocusIndex = index - 1 + focusRequesters[index - 1].requestFocus() + } + } + }, + onBackspace = { + // 빈 박스에서 백스페이스 시 이전 박스로 이동 + if (index > 0) { + val prevIndex = index - 1 + currentFocusIndex = prevIndex + focusRequesters[prevIndex].requestFocus() + } + }, + onFocusChanged = { isFocused -> + if (isFocused) { + currentFocusIndex = index + } + }, + isFocused = currentFocusIndex == index, + modifier = Modifier + .weight(1f) + .focusRequester(focusRequesters[index]) + ) + } + } + } + + // 하단 확인 버튼 + CommonLoginButton( + text = stringResource(Res.string.confirm_button), + onClick = { onVerificationComplete(fullCode) }, + enabled = isComplete, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + +@Preview +@Composable +fun VerificationCodeScreenPreview() { + EmailVerificationScreen( + modifier = Modifier, + onNavigateBack = {}, + onVerificationComplete = { code -> + // 인증번호 처리 로직 + } + ) +} \ No newline at end of file From 42cc4b0e147c498d554e7edc86c99d056bbb9c27 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:14:13 +0900 Subject: [PATCH 13/22] =?UTF-8?q?[ui]:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/PasswordInputScreen.kt | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt new file mode 100644 index 0000000..796359c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt @@ -0,0 +1,145 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.CommonLoginInputField +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.ic_back +import whosinclient.composeapp.generated.resources.next_button +import whosinclient.composeapp.generated.resources.password_confirm_label +import whosinclient.composeapp.generated.resources.password_confirm_placeholder +import whosinclient.composeapp.generated.resources.password_input_label +import whosinclient.composeapp.generated.resources.password_input_placeholder +import whosinclient.composeapp.generated.resources.password_input_title + +@Composable +fun PasswordInputScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onPasswordComplete: (String, String) -> Unit +) { + var password by remember { mutableStateOf("") } + var confirmPassword by remember { mutableStateOf("") } + + val isComplete = password.isNotBlank() && confirmPassword.isNotBlank() + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier.size(14.dp) + ) + } + + Text( + text = stringResource(Res.string.password_input_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + // 비밀번호 입력 라벨 + Text( + text = stringResource(Res.string.password_input_label), + fontWeight = FontWeight.W500, + fontSize = 16.sp, + color = Color.Black, + modifier = Modifier + .padding(bottom = 10.dp) + ) + + // 비밀번호 입력 필드 + CommonLoginInputField( + value = password, + onValueChange = { password = it }, + placeholder = stringResource(Res.string.password_input_placeholder), + isPassword = true, + modifier = Modifier.padding(bottom = 10.dp) + ) + + // 비밀번호 확인 라벨 + Text( + text = stringResource(Res.string.password_confirm_label), + fontWeight = FontWeight.W500, + fontSize = 16.sp, + color = Color.Black, + modifier = Modifier + .padding(bottom = 10.dp) + ) + + // 비밀번호 확인 입력 필드 + CommonLoginInputField( + value = confirmPassword, + onValueChange = { confirmPassword = it }, + placeholder = stringResource(Res.string.password_confirm_placeholder), + isPassword = true + ) + } + + // 하단 다음 버튼 + CommonLoginButton( + text = stringResource(Res.string.next_button), + onClick = { onPasswordComplete(password, confirmPassword) }, + enabled = isComplete, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + +@Preview +@Composable +fun PasswordInputScreenPreview() { + PasswordInputScreen( + modifier = Modifier, + onNavigateBack = {}, + onPasswordComplete = { password, confirmPassword -> + // 비밀번호 처리 로직 + } + ) +} \ No newline at end of file From 00d6290912f52a19c62476cdd03b13df0d7fb1af Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:14:28 +0900 Subject: [PATCH 14/22] =?UTF-8?q?[ui]:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=97=8C=20?= =?UTF-8?q?(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/login/NicknameInputScreen.kt | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt new file mode 100644 index 0000000..f081da3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt @@ -0,0 +1,123 @@ +package org.whosin.client.presentation.auth.login + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.CommonLoginInputField +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.ic_back +import whosinclient.composeapp.generated.resources.next_button +import whosinclient.composeapp.generated.resources.nickname_input_placeholder +import whosinclient.composeapp.generated.resources.nickname_input_title +import whosinclient.composeapp.generated.resources.nickname_welcome_title + +@Composable +fun NicknameInputScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onNavigateToClubCode: (String) -> Unit = {} +) { + var nickname by remember { mutableStateOf("") } + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier.size(18.dp) + ) + } + + Text( + text = stringResource(Res.string.nickname_welcome_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = stringResource(Res.string.nickname_input_title), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + CommonLoginInputField( + value = nickname, + onValueChange = { newValue -> + // 8자 제한 + if (newValue.length <= 8) { + nickname = newValue + } + }, + placeholder = stringResource(Res.string.nickname_input_placeholder), + maxLength = 8 + ) + } + + // 하단 다음 버튼 + CommonLoginButton( + text = stringResource(Res.string.next_button), + onClick = { onNavigateToClubCode(nickname) }, + enabled = nickname.isNotBlank(), + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + +@Preview +@Composable +fun NicknameInputScreenPreview() { + NicknameInputScreen( + modifier = Modifier, + onNavigateBack = {}, + onNavigateToClubCode = { nickname -> + // 닉네임 처리 로직 + } + ) +} \ No newline at end of file From a3517131d31944cd84e07cc1e353a46046853e3c Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:15:20 +0900 Subject: [PATCH 15/22] =?UTF-8?q?[ui]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/clubcode/ClubCodeInputScreen.kt | 294 ++++++++++++++++++ .../auth/clubcode/ClubCodeState.kt | 7 + 2 files changed, 301 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt new file mode 100644 index 0000000..2b1a911 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -0,0 +1,294 @@ +package org.whosin.client.presentation.auth.clubcode + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.whosin.client.presentation.auth.login.component.CommonLoginButton +import org.whosin.client.presentation.auth.login.component.NumberInputBox +import whosinclient.composeapp.generated.resources.Res +import whosinclient.composeapp.generated.resources.back_button +import whosinclient.composeapp.generated.resources.club_code_confirm_button +import whosinclient.composeapp.generated.resources.club_code_error_message +import whosinclient.composeapp.generated.resources.club_code_title_1 +import whosinclient.composeapp.generated.resources.club_code_title_2 +import whosinclient.composeapp.generated.resources.confirm_button +import whosinclient.composeapp.generated.resources.ic_back + + +@Composable +fun ClubCodeInputScreen( + modifier: Modifier = Modifier, + onNavigateBack: () -> Unit = {}, + onNavigateToHome: (String) -> Unit = {}, + onVerifyClubCode: (String) -> Unit = {}, + onErrorReset: () -> Unit = {}, + verificationState: ClubCodeState = ClubCodeState.INPUT, + clubName: String = "" +) { + var clubCode by remember { mutableStateOf(arrayOf("", "", "", "", "", "")) } + var currentFocusIndex by remember { mutableStateOf(0) } + val currentState = verificationState + val focusRequesters = remember { List(6) { FocusRequester() } } + val keyboardController = LocalSoftwareKeyboardController.current + + LaunchedEffect(Unit) { + delay(300) + focusRequesters[0].requestFocus() + keyboardController?.show() + } + + // 화면 종료 시 키보드 숨기기 + DisposableEffect(Unit) { + onDispose { + keyboardController?.hide() + } + } + + LaunchedEffect(currentState) { + if (currentState == ClubCodeState.ERROR) { + delay(1000) + clubCode = arrayOf("", "", "", "", "", "") + currentFocusIndex = 0 + focusRequesters[0].requestFocus() + onErrorReset() + } + } + + val fullCode = clubCode.joinToString("") + val isComplete = fullCode.length == 6 + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(top = 30.dp), + horizontalAlignment = Alignment.Start + ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + .size(24.dp) + ) { + Icon( + painter = painterResource(Res.drawable.ic_back), + contentDescription = stringResource(Res.string.back_button), + tint = Color.Black, + modifier = Modifier.size(18.dp) + ) + } + + // 제목 + Text( + text = stringResource(Res.string.club_code_title_1), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 4.dp) + ) + + Text( + text = stringResource(Res.string.club_code_title_2), + fontWeight = FontWeight.W600, + fontSize = 24.sp, + color = Color.Black, + modifier = Modifier.padding(bottom = 68.dp) + ) + + // 6자리 번호 입력 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + repeat(6) { index -> + NumberInputBox( + value = clubCode[index], + onValueChange = { input -> + if (input.length <= 1 && input.all { it.isDigit() }) { + val newCode = clubCode.copyOf() + val wasEmpty = clubCode[index].isEmpty() + newCode[index] = input + clubCode = newCode + + if (input.isNotEmpty() && index < 5) { + currentFocusIndex = index + 1 + focusRequesters[index + 1].requestFocus() + } else if (input.isEmpty() && !wasEmpty && index > 0) { + currentFocusIndex = index - 1 + focusRequesters[index - 1].requestFocus() + } + } + }, + onBackspace = { + if (index > 0) { + val prevIndex = index - 1 + currentFocusIndex = prevIndex + focusRequesters[prevIndex].requestFocus() + } + }, + onFocusChanged = { isFocused -> + if (isFocused) { + currentFocusIndex = index + } + }, + borderColor = when (currentState) { + ClubCodeState.ERROR -> Color(0xFFFF3636) + else -> Color(0xFFE5E5E5) + }, + focusedBorderColor = when (currentState) { + ClubCodeState.ERROR -> Color(0xFFFF3636) + else -> Color(0xFFF89531) + }, + isFocused = currentFocusIndex == index, + modifier = Modifier + .weight(1f) + .focusRequester(focusRequesters[index]) + ) + } + } + + // 에러 메시지 + if (currentState == ClubCodeState.ERROR) { + Text( + text = stringResource(Res.string.club_code_error_message), + fontSize = 16.sp, + fontWeight = FontWeight.W500, + color = Color(0xFFFF3636), + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = 12.dp, bottom = 12.dp) + .fillMaxWidth() + ) + } + + Box( + modifier = Modifier + .padding(top = if (currentState != ClubCodeState.ERROR) 48.dp else 0.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + CommonLoginButton( + text = stringResource(Res.string.club_code_confirm_button), + onClick = { + if (isComplete) { + onVerifyClubCode(fullCode) + } + }, + enabled = isComplete, + modifier = Modifier + .size(width = 108.dp, height = 44.dp) + ) + } + + + if (currentState == ClubCodeState.SUCCESS) { + // 동아리 정보 박스 + Box( + modifier = Modifier + .padding(top = 56.dp) + .padding(horizontal = 40.dp) + .fillMaxWidth() + .height(88.dp) + .background( + color = Color(0xFFFBFBFB), + shape = RoundedCornerShape(8.dp) + ) + .border( + width = 1.dp, + color = Color(0xFFE5E5E5), + shape = RoundedCornerShape(8.dp) + ), + + contentAlignment = Alignment.Center + ) { + Text( + text = clubName, + fontSize = 16.sp, + fontWeight = FontWeight.W500, + color = Color.Black, + textAlign = TextAlign.Center + ) + } + } + } + + // 하단 확인 버튼 + CommonLoginButton( + text = stringResource(Res.string.confirm_button), + onClick = { + if (currentState == ClubCodeState.SUCCESS) { + onNavigateToHome(clubName) + } + }, + enabled = currentState == ClubCodeState.SUCCESS, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .padding(bottom = 36.dp) + ) + } +} + +@Preview +@Composable +fun ClubCodeInputScreenPreview() { + var verificationState by remember { mutableStateOf(ClubCodeState.INPUT) } + var clubName by remember { mutableStateOf("") } + + ClubCodeInputScreen( + modifier = Modifier, + verificationState = verificationState, + clubName = clubName, + onNavigateBack = {}, + onNavigateToHome = { name -> println("Navigate to home with: $name") }, + onVerifyClubCode = { code -> + if (code == "123456") { + verificationState = ClubCodeState.SUCCESS + clubName = "메이커스팜" + } else { + verificationState = ClubCodeState.ERROR + } + }, + onErrorReset = { + verificationState = ClubCodeState.INPUT + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt new file mode 100644 index 0000000..3dc43ce --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt @@ -0,0 +1,7 @@ +package org.whosin.client.presentation.auth.clubcode + +enum class ClubCodeState { + INPUT, // 입력 중 + SUCCESS, // 성공 (동아리 정보 표시) + ERROR // 실패 (에러 메시지 표시) +} \ No newline at end of file From 58aa131893dfe1ca3ccf50be573a9a3741fdf4b3 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:15:32 +0900 Subject: [PATCH 16/22] =?UTF-8?q?[ui]:=20string=20=EC=B6=94=EC=B6=9C=20(#1?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composeResources/values/strings.xml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 6902b22..b86f717 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1,4 +1,45 @@ + + 로그인 + E-mail + password + 이메일을 입력해주세요 + 비밀번호를 입력해주세요 + 로그인 + 회원가입 + 비밀번호 찾기 + + + 이메일을 입력해주세요. + 다음 + 뒤로가기 + + + 비밀번호 찾기 + 이메일 전송 + + + 인증번호를 입력해주세요. + 확인 + + + 비밀번호를 입력해주세요. + 비밀번호 입력 + 비밀번호 확인 + 비밀번호 입력 (8~20자 영문, 숫자) + 비밀번호 확인 (8~20자 영문, 숫자) + + + 반가워요! + 닉네임을 입력해주세요. + 닉네임을 입력해주세요 + + + 동아리/학과의 + 고유 번호를 입력해주세요. + 고유 번호가 일치하지 않습니다. + 확인하기 + 지금 %1$s의 재실자는? From 2cf280d500acfcbe9b04a201c90e72401be78dbf Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:15:50 +0900 Subject: [PATCH 17/22] =?UTF-8?q?[feat]:=20Auth=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/core/navigation/WhosInNavGraph.kt | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt index 956f52a..5a9d3ce 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt @@ -6,7 +6,14 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navigation -import org.whosin.client.presentation.auth.LoginScreen +import org.whosin.client.presentation.auth.clubcode.ClubCodeInputScreen +import org.whosin.client.presentation.auth.login.EmailVerificationScreen +import org.whosin.client.presentation.auth.login.LoginScreen +import org.whosin.client.presentation.auth.login.NicknameInputScreen +import org.whosin.client.presentation.auth.login.FindPasswordScreen +import org.whosin.client.presentation.auth.login.PasswordInputScreen +import org.whosin.client.presentation.auth.login.SignupScreen +import org.whosin.client.presentation.auth.login.SplashScreen import org.whosin.client.presentation.home.HomeScreen import org.whosin.client.presentation.mypage.MyPageScreen @@ -21,13 +28,95 @@ fun WhosInNavGraph( ) { /* 인증,인가 화면들을 위한 별도의 graph */ navigation( - startDestination = Route.Login, + startDestination = Route.Splash, ) { + composable { + SplashScreen( + modifier = modifier, + onNavigateToLogin = { + navController.navigate(Route.Login) { + popUpTo(Route.Splash) { inclusive = true } + } + } + ) + } + composable { LoginScreen( modifier = modifier, onNavigateToHome = { navController.navigate(Route.Home) + }, + onNavigateToFindPassword = { + navController.navigate(Route.FindPassword) + }, + onNavigateToSignup = { + navController.navigate(Route.Signup) + } + ) + } + + composable { + FindPasswordScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onPasswordResetComplete = { + navController.navigate(Route.Login) { + popUpTo(Route.FindPassword) { inclusive = true } + } + } + ) + } + + composable { + SignupScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onNavigateToEmailVerification = { email -> + navController.navigate(Route.EmailVerification) + } + ) + } + + composable { backStackEntry -> + + EmailVerificationScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onVerificationComplete = { + navController.navigate(Route.PasswordInput) + } + ) + } + + composable { + PasswordInputScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onPasswordComplete = { password, confirmPassword -> + navController.navigate(Route.NicknameInput) + } + ) + } + + composable { + NicknameInputScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onNavigateToClubCode = { + navController.navigate(Route.ClubCodeInput) + } + ) + } + + composable { + ClubCodeInputScreen( + modifier = modifier, + onNavigateBack = { navController.navigateUp() }, + onNavigateToHome = { + navController.navigate(Route.Home) { + popUpTo(Route.AuthGraph) { inclusive = true } + } } ) } From 05dd579c74c0dbe9c8cf8cbc14e57094e9502376 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:26:47 +0900 Subject: [PATCH 18/22] =?UTF-8?q?[ui]:=20=EB=B0=94=ED=85=80=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=ED=8C=A8=EB=94=A9=20=EC=B6=94=EA=B0=80=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/clubcode/ClubCodeInputScreen.kt | 2 +- .../client/presentation/auth/login/EmailVerificationScreen.kt | 2 +- .../whosin/client/presentation/auth/login/FindPasswordScreen.kt | 2 +- .../client/presentation/auth/login/NicknameInputScreen.kt | 2 +- .../client/presentation/auth/login/PasswordInputScreen.kt | 2 +- .../client/presentation/auth/login/SignupEmailInputScreen.kt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt index 2b1a911..65ffc7f 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt @@ -262,7 +262,7 @@ fun ClubCodeInputScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt index 3c73be9..4f5380e 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt @@ -156,7 +156,7 @@ fun EmailVerificationScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt index 8c54388..c8d3766 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt @@ -90,7 +90,7 @@ fun FindPasswordScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt index f081da3..db7e420 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt @@ -105,7 +105,7 @@ fun NicknameInputScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt index 796359c..a700a5b 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt @@ -127,7 +127,7 @@ fun PasswordInputScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt index 436b38a..e0bdc43 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt @@ -90,7 +90,7 @@ fun SignupScreen( modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 36.dp) + .padding(bottom = 52.dp) ) } } From 0512cca2f3e69f981b81f76e27308472511a6d5d Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 14:30:10 +0900 Subject: [PATCH 19/22] =?UTF-8?q?[ui]:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=ED=99=94=EB=A9=B4=20=ED=8C=A8?= =?UTF-8?q?=EB=94=A9=20=EC=88=98=EC=A0=95=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/login/NicknameInputScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt index db7e420..fe9b885 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt @@ -81,7 +81,7 @@ fun NicknameInputScreen( fontWeight = FontWeight.W600, fontSize = 24.sp, color = Color.Black, - modifier = Modifier.padding(bottom = 68.dp) + modifier = Modifier.padding(bottom = 32.dp) ) CommonLoginInputField( From a7f604fea20838781f986cc8ad2588f24d5fe9f7 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 21:49:47 +0900 Subject: [PATCH 20/22] =?UTF-8?q?[ui]:=20InputField=208=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B4=EB=82=B4=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/login/NicknameInputScreen.kt | 5 +---- .../auth/login/component/CommonLoginInputField.kt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt index fe9b885..e023f9c 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt @@ -87,10 +87,7 @@ fun NicknameInputScreen( CommonLoginInputField( value = nickname, onValueChange = { newValue -> - // 8자 제한 - if (newValue.length <= 8) { - nickname = newValue - } + nickname = newValue }, placeholder = stringResource(Res.string.nickname_input_placeholder), maxLength = 8 diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt index 94b9e5b..4c9d1c8 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt @@ -40,7 +40,7 @@ fun CommonLoginInputField( val isMaxLengthReached = maxLength != null && value.length >= maxLength OutlinedTextField( value = value, - onValueChange = onValueChange, + onValueChange = { if (maxLength == null || it.length <= maxLength) onValueChange(it) }, placeholder = { Text( text = placeholder, From bc1555a6d4a21ed9a4b8806a46e9351dae342f5e Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 21:50:13 +0900 Subject: [PATCH 21/22] =?UTF-8?q?[refactor]:=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=88=98=EC=A0=95=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composeApp/build.gradle.kts | 3 +-- gradle/libs.versions.toml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e5932c2..f58a003 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -63,7 +63,7 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.runtimeCompose) - implementation(libs.androidx.material.icons.extended) + implementation(compose.materialIconsExtended) // Navigation implementation(libs.navigation.compose) // Ktor 핵심 클라이언트 @@ -122,7 +122,6 @@ android { } dependencies { - implementation(libs.androidx.material.icons.extended) debugImplementation(compose.uiTooling) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32d753f..b1fd109 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,6 @@ navigation-compose = "2.9.0-beta03" ktor = "3.2.3" kotlinx-serialization = "1.9.0" koin = "4.1.0" -materialIconsExtended = "1.7.8" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -50,7 +49,6 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" } -androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "materialIconsExtended" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 34be63b4bb2150fc2cd2b4fd22b38dd0edb87bb5 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 23 Sep 2025 21:50:32 +0900 Subject: [PATCH 22/22] =?UTF-8?q?[ui]:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=ED=99=94=EB=A9=B4=20=ED=8C=A8?= =?UTF-8?q?=EB=94=A9=20=EC=88=98=EC=A0=95=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/presentation/auth/login/PasswordInputScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt index a700a5b..4aa1c5a 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt @@ -69,7 +69,7 @@ fun PasswordInputScreen( painter = painterResource(Res.drawable.ic_back), contentDescription = stringResource(Res.string.back_button), tint = Color.Black, - modifier = Modifier.size(14.dp) + modifier = Modifier.size(18.dp) ) }