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 0000000..c9463f6 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/AppIcon_round.png differ 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