From 4697119c5838a8a8d4b9110ae0c476214c84056b Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 18 Sep 2024 05:56:58 +0200 Subject: [PATCH] Implemented LoginScreen --- .../drawable/AppIcon_round.png | Bin 0 -> 5102 bytes .../kotlin/net/codinux/banking/ui/App.kt | 14 ++- .../net/codinux/banking/ui/config/DI.kt | 4 + .../banking/ui/forms/PasswordTextField.kt | 12 ++- .../codinux/banking/ui/screens/LoginScreen.kt | 80 ++++++++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/AppIcon_round.png create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt diff --git a/composeApp/src/commonMain/composeResources/drawable/AppIcon_round.png b/composeApp/src/commonMain/composeResources/drawable/AppIcon_round.png new file mode 100644 index 0000000000000000000000000000000000000000..c9463f6263c81858ca501367d654f897c17cfa6f GIT binary patch literal 5102 zcmV4z0nMHQ-cZ%rc+?he`~UAVpxx z&aJTsSaR;?L0~lbqx69pLg>Mz2az728{8v^9AqqUkP&l;uCBp=|Ask9YzN3Xu~-p_ z;kAUg3S=FI8sJJ`S-?q7AUp@`2eY39&k`#91x-yu0q4kSS^#s|uwezE#0HQp;BEz3 z4;0vrHG?tWd4y-c?8ca%)7*K;cBo9I1#q#3hC+t>w}P`B%yuAZJJNy(zXN`Z;ZX`p zo}#gF*mk5$q6GlZaCI5O&YKY4g5erqvF%7cISc#*F}oObzE4|QzwJP_ z9VSTmQ2?=65$?#%2)6=tw&P^DHsI?NmfW8Y-xqSb0JhaGBhGt4?f@>a9Vg421o;Lb z_si_=_@nJGe#y-O*j#%#3Hf`3cL9aA<7BxZF!zxN-$`rhA8d#5T5c7<=9=ZWkq?9U zYqaBgZ4}`?9P_DM+9Tvf0mNbzn2}EaA3!_4--f{a3*oWP^348Iw!b5Q{zJE>k# zz~*g@95`TX2ZBr)0@c(kVR87sfH&HXq5|;*p`y34d-rMELF^YmG`f@)NM zJ7{Tdwy}9TM}aoi{wa?9FWUK)P=@dn8|z+eWAnimK&5Fdl|xJ~hHZWyBK5Zbk|(*E zfXjW|j>=VsNQ7dvwjT3q1O6QaibfX`aUVuIznZ_*gc6TZQ{&tBMt=%m=gtrj^8m<3 zU)opsB+8P=ckrpN`g1jCN#R|j{otpQExlxmi4b7;qFDx@+yCv2I^^U|D{(AdN;0WSX)fOeHiWh%0EjH*+n$! z*$h736~J)em(bf7mEVk6OT_(xSIv1d1d7$&h~Ys`+EOYd<_4NOez4G17G3~bYA+!n zUFfCFDp=jbrG~b)qYG(d;X|M?`3BngRq(2axc|P;Miy28o1*X1C$=ej&PwU+0k*CyXDsq-^swig3JZj?GKaDfp{g=O zWo7c`$_j@_Wa>YAB4Idv26}oEoa}Mw?|13zH}v%<=o>JQ5l_<0bfy<6d>Ktmy{WDd zPOJT~$S2Xxe_kjAON)~oysE;XwA7)jH2J!CNqV0xp^#8k3T35XUb1HLdt(Xc>vQSu zb?NKBP#oPo3C;{^Wn3T?xFa6}KAQ3xDYu=xz45m|(L9o6wc;hoPF+?il$JUyt8}QW z2vJ%hh~4o7-IKBO3pb86w6)y_0;zd*6ySO-ddXmM_dn&=`Xz*DBQiLlX1^yP=`8bk2oxh+!xU@P%Ww}FnnL|anP*EOYNt)yGTy|m4 zG5J8CF)(o2-D09Z_z56rdD>;=%!P>67=XORL>vNZGN{mT_4OS9~QMa=EQVzYjD&)!>x z2HpuW&G7}LvchuA=wHmX!0ZK(WuN{^O(AC1K0I>)Y^+@m)OnppsX!Ul6I)+9)7vu_ zKuF&2OCY6!=DN4c^w#tRP*bx6!`poeq*RcEcg*xGq3H{tD10OOfGd?dN{N_hCt;ty z0ED;s8b_(%P1~oZ7C^(*Wx${LokppE?I0R0z4(_?3t-5(ku1OHO{r-V5}rB?npyx3 zHw6+$sh~OXmMOoQ^s1qu5W_Wr1yU+V#%yQDODBzPCKtfSz^eey9zCs8hFL;S$(D(~ zxOfP(Bfv091y8tk;>*be;7)!s8x0L-{oPC%D<}SKW%7sog#y@8dkMlT0uSV|wvJIu z4*Wl58e?EzNA}zjHj69Suwljciwn0zCS1Z=MIC)6*i-IE@Y4r_cKZo?}8N*`_57TpU_LEUBf7wdz{^HG4Ru+4@ z{qFA5-0|W2dH&bOJZ;w^LtKg-iWjY!d)e#}zIw#-&JQ3(hVWH$Ukb2s>mtuPKfvN* zp?=HUkFdV|1F!MC^8-{>E#1E zDjkf`0#Ny?(gCV$r%=iYktzqo`-8?xWdmcDI$-oEa4KIda)3OWAy=vM3bX)JKGOnF zd96Sn9HR1?1LVZhj+B~*5iJ0f&$IwkUK?>hMr@~0%92qB%xT*xl(J;NLFl%fLMb~; zw}VTM?HEegAw3QvS^z3P2|W(z5J=@I*Yr5x9;X*Cb3oi?9;E^_NN(IXha+}6qTquCn z)?>i`*+`^RfcCSe>-?1ulOK#_cp~sPN(F0DAHV3yaPoMNNt6nlky#3$qW@>WV32u~ z3eX@G{gaM%PA-7Qe=`F7G{{6s1;+5yxjug0)Q97W*%eqIrGn&`?@jsDl!e?y4+FzN z!?=qcyZDz=3!tTC0QgZ5iIfV^W9RzzB-76iAo76kag++u41Icr2{INvgrU#R zJW2(qpHZd^e_*BpXlfcl%2r<eA`;mhjkT$?!?_5(gsS`p~d@uI)K=?u+9leAs*fI$Gy`dgV92{WS5o_W|PZ1mC>> zY0umB*a_m?na&E5_9p71A4JSo(q1n>QtaX^Pe90{1{2_(2w~2o_IXiKDy&=;BIMEb z6A3taIKe=FYI~Pe)fLoUeL0bcHv!`D1Rb5f=kSqUPula*U((#3)_2wP3t;EY5P$e> z2e2;fb(Qjqkxnjq@p^XcN^AF*egOb%s@)Fq)V$VJ%6kT!SJS+&G2IpC_l>c+Bx~Gs#hFGfoq0~Yda|?Sq z_Re?p`F#hNXj9$ez_kl*N2&BZN^|=g7t+AO&l;J?yTPP<;z22ooFox?|3VvCcmcGu z9LJbj7u=3gsWZSW=N{?5Al{rQIdbe*T)OfSAnHkbO3m&oG`D}ri-x?raAt7q!@vR0 z+EZ$JT}Z*akNRB9y8`Iy8pOD70{T5`Q>lpzeF!%tXEpVPx0g<~bp9I5J4uewl=70n z;k`7q|HjKky}DfNg2TstMfHjTM7DX}w#r||e46GC&o9UK=i>RxUiwMk2fnne^3j8| zbbQK>h8KP!Y$mZ-5oY8kz$V|ep>m(FkHN79y1IP1)5pI80N7T$jJPxbFY$FdDo4pd zOgNU#9bRc~!LNqTtFOHR;c1Wx-?yc5NjDM&}z)X&AsY zJN)ka5JUk0?CI>pajyfi;jU_B*I69%M)r2>3u0rz6aYX|=N^pQgg#(P&1@LV4eaUo zc~Bb+t^fd9+I|GiwdkHeO?4I{Hze;i437U_f~Qj--HMpUfD$X)SDB_Coa<=bw>#(! z+bIBm`no8>6F`-<3@Q_x0@*=xTZ@g22Rj6ssHMFPV_pRuvX(*FCHxMN&9*u}*en1@ z9wLp)1~7r|`jl;EFK%RW5QjQvW48bR+S+MCzrQ3vv?shhCmlPa=eF2SN{i$Ed|*~rWUH2 zn{Yp8$KFg^r}gpvbTYqV#p+&%;XYu6?IzmClK=ynmV#(THVb24lt9$M-Q;N>RwE! z9H=AP3OKjzA}-glx4p@BkaT2k2ijQoY8<`@)Yy)a*Nk)`@}c=YpWn9ZEr2A4SnWG7 z^3Uk7C}=$x`81ck_?~p9CCVh(UI0lASC=v5d>r96V6p8uIc^Z-9^%|dTU*vY1u!=X zVBEIaWyGDo1#<^bWIIld8AZ5{QTd1DoYA=utx$aIJ{>I%TK7&Evnx`3D%}M*&P!UwZ}Oyc5j(fJ)nOGDJVZ1K@m(rhOf@!vrb+ z3Se9`x|pzgJtA)fb^wL8BL!>-cpP{DDR{)oXYOZ{{4Icq>g!7&em!s#!giq8b|jyi z0iHtSzi|p5VfXISwgcIw1#q#3hA=~?HzB+M%yzQsODBX9tu3(S6yE((_(ps{f{;2c>^3&1O}SP|~<8VIchcMZZNV3vbaVyMKC zN-#wjN=ROX8{xutjWa+TNFWSgIE$edp$F&&PGaN;$RWf%NMXrA?dab4e{I@-irWTA Q+5i9m07*qoM6N<$f>O}TRsaA1 literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt index 23c74fb..98870fc 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/App.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.DI +import net.codinux.banking.ui.model.settings.AppAuthenticationMethod +import net.codinux.banking.ui.screens.LoginScreen import net.codinux.banking.ui.screens.MainScreen import net.codinux.log.LoggerFactory import org.jetbrains.compose.ui.tooling.preview.Preview @@ -25,13 +27,23 @@ fun App() { val colors = MaterialTheme.colors.copy(primary = Colors.Primary, primaryVariant = Colors.PrimaryDark, onPrimary = Color.White, secondary = Colors.Accent, secondaryVariant = Colors.Accent, onSecondary = Color.White) + val appSettings = DI.uiState.appSettings.collectAsState().value + + var isLoggedIn by remember(appSettings.authenticationMethod) { mutableStateOf(appSettings.authenticationMethod == AppAuthenticationMethod.None) } + var isInitialized by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() MaterialTheme(colors = colors, typography = typography) { - MainScreen() + if (isLoggedIn == false) { + LoginScreen(appSettings) { + isLoggedIn = true + } + } else { + MainScreen() + } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt index 4865136..2f2641e 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt @@ -43,6 +43,10 @@ object DI { fun setRepository(repository: BankingRepository) { this.bankingRepository = repository + + repository.getAppSettings()?.let { // otherwise it's the first app start, BankingService will take care of this case + uiState.appSettings.value = it + } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt index 880d083..7445ea9 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/PasswordTextField.kt @@ -17,7 +17,16 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp @Composable // try BasicSecureTextField -fun PasswordTextField(password: String = "", label: String = "Passwort", modifier: Modifier = Modifier, keyboardOptions: KeyboardOptions? = null, forceHidePassword: Boolean? = null, onEnterPressed: (() -> Unit)? = null, onChange: (String) -> Unit) { +fun PasswordTextField( + password: String = "", + label: String = "Passwort", + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions? = null, + isError: Boolean = false, + forceHidePassword: Boolean? = null, + onEnterPressed: (() -> Unit)? = null, + onChange: (String) -> Unit +) { var passwordVisible by remember { mutableStateOf(false) } @@ -30,6 +39,7 @@ fun PasswordTextField(password: String = "", label: String = "Passwort", modifie onValueChange = { onChange(it) }, label = { Text(label) }, modifier = modifier.fillMaxWidth(), + isError = isError, visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), trailingIcon = { val visibilityIcon = if (passwordVisible) { diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt new file mode 100644 index 0000000..a7ec3d6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/LoginScreen.kt @@ -0,0 +1,80 @@ +package net.codinux.banking.ui.screens + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import bankmeister.composeapp.generated.resources.* +import bankmeister.composeapp.generated.resources.Res +import net.codinux.banking.ui.forms.PasswordTextField +import net.codinux.banking.ui.model.settings.AppAuthenticationMethod +import net.codinux.banking.ui.model.settings.AppSettings +import net.codinux.banking.ui.service.PasswordService +import org.jetbrains.compose.resources.imageResource + +@Composable +fun LoginScreen(appSettings: AppSettings, onLoginSuccess: () -> Unit) { + + var password by remember { mutableStateOf("") } + + var showError by remember { mutableStateOf(false) } + + + fun checkPassword() { + if (appSettings.hashedPassword != null && PasswordService.checkPassword(password, appSettings.hashedPassword!!)) { + onLoginSuccess() + } else { + showError = true + } + } + + + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image(imageResource(Res.drawable.AppIcon_round), "Bankmeister's app icon", Modifier.size(144.dp).padding(bottom = 32.dp)) + + if (appSettings.authenticationMethod == AppAuthenticationMethod.Password) { + Text("Bitte geben Sie Ihr Passwort ein um die App zu entsperren", style = MaterialTheme.typography.h5, textAlign = TextAlign.Center) + Spacer(modifier = Modifier.height(24.dp)) + + PasswordTextField( + password = password, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + onEnterPressed = { checkPassword() }, + isError = showError + ) { + password = it + showError = false + } + + if (showError) { + Spacer(modifier = Modifier.height(16.dp)) + Text("Passwort ist falsch", color = MaterialTheme.colors.error, fontSize = 18.sp) + } + + Button(modifier = Modifier.padding(top = 24.dp).width(300.dp).height(50.dp), onClick = { checkPassword() }) { + Text("Login", color = Color.White) + } + } + } + } + +} \ No newline at end of file