diff --git a/composeApp/src/commonMain/composeResources/drawable/otp.png b/composeApp/src/commonMain/composeResources/drawable/otp.png new file mode 100644 index 0000000..0bdcf21 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/otp.png differ diff --git a/composeApp/src/commonMain/composeResources/values-ar/string.xml b/composeApp/src/commonMain/composeResources/values-ar/string.xml index 4659eed..3319494 100644 --- a/composeApp/src/commonMain/composeResources/values-ar/string.xml +++ b/composeApp/src/commonMain/composeResources/values-ar/string.xml @@ -8,7 +8,7 @@ مرحبًا بك في صنعة 👋 أدخل رقم هاتفك للمتابعة - +٢٠ ٠٠٠ - ٠٠٠ - ٠٠٠٠ + ٠٠٠ - ٠٠٠ - ٠٠٠٠ الشروط والأحكام سياسة الخصوصية بالمتابعة فإنك توافق على @@ -24,4 +24,23 @@ التالى تخطى + + أدخل رمز التحقق + لقد أرسلنا رمزًا مكوّنًا من 6 أرقام إلى رقم هاتفك + تحقق + لم تستلم الرمز؟ + إعادة إرسال الرمز بعد + إعادة إرسال الرمز + + + + + + أدخل رمز التحقق + لقد أرسلنا رمزًا مكوّنًا من 4 أرقام إلى رقم هاتفك + تحقق + لم تستلم الرمز؟ + إعادة إرسال الرمز بعد + إعادة إرسال الرمز + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/values/string.xml b/composeApp/src/commonMain/composeResources/values/string.xml index afd1afe..ccee94f 100644 --- a/composeApp/src/commonMain/composeResources/values/string.xml +++ b/composeApp/src/commonMain/composeResources/values/string.xml @@ -9,7 +9,7 @@ Welcome to San3a 👋 Enter your phone number to\n continue - +20 000 - 000 - 0000 + 000 - 000 - 0000 Terms and Conditions Privacy Policy By continuing, you agree on our @@ -20,12 +20,27 @@ logo icon egypt flag + + Enter Verification Code + We sent 6-digit code to your phone number + Verify + Didn't receive code? + Resend Code after + Resend Code + Get Started Next Skip + + Enter Verification Code + We sent 4-digit code to your phone number + Verify + Didn't receive code? + Resend Code after + Resend Code What do you usually need help with? @@ -33,4 +48,5 @@ What services do you offer? Choose your specialties to get relevant job requests. You can change this later. + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/BackButton.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/BackButton.kt new file mode 100644 index 0000000..041d77d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/BackButton.kt @@ -0,0 +1,48 @@ +package org.example.project.presentation.designsystem.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import crafto.composeapp.generated.resources.Res +import crafto.composeapp.generated.resources.arrow_left +import org.example.project.presentation.designsystem.textstyle.AppTheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun BackButton( + onClick : () -> Unit, + modifier: Modifier = Modifier, +){ + Box( + modifier = modifier + ){ + Icon( + painter = painterResource(Res.drawable.arrow_left), + contentDescription = "arrow left icon", + modifier = Modifier.align(Alignment.Center).clickable(onClick = onClick) + .padding(12.dp) + ) + } +} + +@Preview +@Composable +private fun BackButtonPreview(){ + AppTheme(isDarkTheme = false) { + BackButton( + modifier = Modifier.background( + color = AppTheme.craftoColors.background.card, + shape = RoundedCornerShape(AppTheme.craftoRadius.full) + ), + onClick = {} + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/TextField.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/TextField.kt index fdb59f1..e294fa1 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/TextField.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/designsystem/components/TextField.kt @@ -3,6 +3,7 @@ package org.example.project.presentation.designsystem.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,8 +15,16 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.* -import androidx.compose.runtime.* +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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -35,12 +44,10 @@ import org.example.project.presentation.designsystem.textstyle.AppTheme import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.ui.tooling.preview.Preview -private const val s = "Forgot Password?" - @Composable fun TextField( labelText: String? = null, - showDividerLine : Boolean = false, + showDividerLine: Boolean = false, hint: String? = null, text: String, onTextChange: (String) -> Unit, @@ -59,6 +66,7 @@ fun TextField( allowSingleLine: Boolean = true, textAppearance: TextStyle? = null, textTint: Color? = null, + showPhoneCode: Boolean = false, inputKeyboard: KeyboardOptions = KeyboardOptions.Default.copy(imeAction = androidx.compose.ui.text.input.ImeAction.Done), inputActions: KeyboardActions = KeyboardActions.Default, forgotAction: (() -> Unit)? = null, @@ -98,7 +106,7 @@ fun TextField( .background( AppTheme.craftoColors.background.card, RoundedCornerShape(AppTheme.craftoRadius.lg) - ), + ).focusable(), textStyle = textAppearance ?: AppTheme.textStyle.body.medium, placeholder = hint?.let { { @@ -117,9 +125,20 @@ fun TextField( isError = errorState, enabled = enabledState, leadingIcon = { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { startIcon?.invoke() - if(showDividerLine) VerticalDivider() + if (showDividerLine) VerticalDivider() + + if (showPhoneCode) { + Text( + text = "+20", + style = AppTheme.textStyle.body.medium, + color = AppTheme.craftoColors.shade.primary + ) + } } }, trailingIcon = { diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/OTPScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/OTPScreen.kt new file mode 100644 index 0000000..e836720 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/OTPScreen.kt @@ -0,0 +1,264 @@ +package org.example.project.presentation.screens.register + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import crafto.composeapp.generated.resources.Res +import crafto.composeapp.generated.resources.do_not_receive_code +import crafto.composeapp.generated.resources.enter_verification_code +import crafto.composeapp.generated.resources.otp +import crafto.composeapp.generated.resources.resent_code +import crafto.composeapp.generated.resources.sent_message_to_phone +import crafto.composeapp.generated.resources.verify +import org.example.project.presentation.designsystem.components.BackButton +import org.example.project.presentation.designsystem.components.ButtonState +import org.example.project.presentation.designsystem.components.PrimaryButton +import org.example.project.presentation.designsystem.components.SecondaryButton +import org.example.project.presentation.designsystem.textstyle.AppTheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun OTPScreen() { + OTPContent( + phoneNumber = "01279336697", + onVerifyButtonClick = {}, + onResendCodeButtonClick = {}, + onBackButtonClick = {} + ) +} + +@Composable +private fun OTPContent( + modifier: Modifier = Modifier, + phoneNumber: String, + onVerifyButtonClick: () -> Unit, + onResendCodeButtonClick: () -> Unit, + onBackButtonClick: () -> Unit +) { + + var otpList by remember { mutableStateOf(List(6) { "" }) } + val focusManager = LocalFocusManager.current + val focusRequesters = List(otpList.size) { FocusRequester() } + + Box( + modifier = modifier.fillMaxSize().background(AppTheme.craftoColors.background.screen) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .padding(start = 16.dp, top = 16.dp) + .windowInsetsPadding(WindowInsets.statusBars) + .align(Alignment.Start) + ) + { + BackButton( + modifier = Modifier.background( + color = AppTheme.craftoColors.background.card, + shape = RoundedCornerShape(AppTheme.craftoRadius.full) + ), + onClick = onBackButtonClick + ) + } + + Image( + painter = painterResource(Res.drawable.otp), + contentDescription = "otp image", + ) + + Box( + modifier = Modifier.fillMaxSize().background( + color = AppTheme.craftoColors.background.card, + shape = RoundedCornerShape( + topStart = AppTheme.craftoRadius.x5l, + topEnd = AppTheme.craftoRadius.x5l + ), + ).padding(horizontal = 24.dp) + ) { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + .windowInsetsPadding(WindowInsets.ime) + ) { + Text( + text = stringResource(Res.string.enter_verification_code), + style = AppTheme.textStyle.title.medium, + color = AppTheme.craftoColors.shade.primary, + modifier = Modifier.padding(top = 40.dp, bottom = 8.dp) + ) + + Text( + text = stringResource(Res.string.sent_message_to_phone), + style = AppTheme.textStyle.body.mediumRegular, + color = AppTheme.craftoColors.shade.secondary, + ) + Text( + text = phoneNumber, + style = AppTheme.textStyle.body.medium, + color = AppTheme.craftoColors.shade.primary, + modifier = Modifier.padding(bottom = 24.dp) + ) + + Row( + modifier = Modifier.padding(bottom = 24.dp) + ) { + + repeat(otpList.size) { index -> + OTPField( + text = otpList[index], + modifier = Modifier.weight(1f) + .focusRequester(focusRequesters[index]), + onTextChange = { value -> + val digit = value.filter { it.isDigit() }.take(1) + val updateList = otpList.toMutableList().also { list -> + list[index] = digit + } + otpList = updateList + + if (digit.isNotEmpty()) { + if (index < otpList.size - 1) { + focusRequesters[index + 1].requestFocus() + } else { + focusManager.clearFocus() + } + } else if (otpList[index].isEmpty() && value.isEmpty()) { + if (index > 0) { + focusRequesters[index - 1].requestFocus() + } + } + } + ) + } + } + + PrimaryButton( + text = stringResource(Res.string.verify), + enabled = true, + onClick = onVerifyButtonClick, + buttonState = ButtonState.Enable, + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp) + ) + + Text( + text = stringResource(Res.string.do_not_receive_code), + style = AppTheme.textStyle.body.mediumRegular, + color = AppTheme.craftoColors.shade.secondary, + modifier = Modifier + .padding(top = 24.dp, bottom = 12.dp) + .align(Alignment.CenterHorizontally) + ) + + SecondaryButton( + text = stringResource(Res.string.resent_code), + enabled = true, + onClick = onResendCodeButtonClick, + buttonState = ButtonState.Enable, + modifier = Modifier.fillMaxWidth(), + containerColor = AppTheme.craftoColors.shade.quinary, + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp) + ) + } + } + } + } +} + +@Composable +private fun OTPField( + text: String, + hint: String = "0", + modifier: Modifier, + onTextChange: (String) -> Unit +) { + var isFocused by remember { mutableStateOf(false) } + + OutlinedTextField( + value = text, + onValueChange = onTextChange, + modifier = modifier.background(AppTheme.craftoColors.background.card) + .padding(horizontal = 5.dp, vertical = 8.dp).onFocusChanged { focusState -> + isFocused = focusState.isFocused + }, + singleLine = true, + textStyle = AppTheme.textStyle.title.large.copy( + textAlign = TextAlign.Center, + color = AppTheme.craftoColors.shade.primary + ), + shape = RoundedCornerShape(AppTheme.craftoRadius.lg), + placeholder = { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + if (text.isEmpty() && !isFocused) { + Text( + hint, + textAlign = TextAlign.Center, + style = AppTheme.textStyle.title.large, + color = AppTheme.craftoColors.shade.tertiary + ) + } + } + }, + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = AppTheme.craftoColors.shade.primary, + focusedBorderColor = AppTheme.craftoColors.brand.primary, + unfocusedBorderColor = AppTheme.craftoColors.stroke.primary, + errorBorderColor = AppTheme.craftoColors.additional.primaryRed, + errorTextColor = AppTheme.craftoColors.shade.primary, + cursorColor = AppTheme.craftoColors.brand.primary, + errorCursorColor = AppTheme.craftoColors.additional.primaryRed, + disabledTextColor = AppTheme.craftoColors.shade.primary, + disabledBorderColor = AppTheme.craftoColors.stroke.primary, + disabledPlaceholderColor = AppTheme.craftoColors.shade.tertiary, + disabledLabelColor = AppTheme.craftoColors.shade.tertiary, + ), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ) + ) +} + + +@Preview +@Composable +private fun OTPScreenPreview() { + AppTheme { + OTPScreen() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt index d319231..fdaf06f 100644 --- a/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/example/project/presentation/screens/register/RegisterScreen.kt @@ -14,11 +14,18 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon 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.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import crafto.composeapp.generated.resources.Res @@ -26,11 +33,11 @@ import crafto.composeapp.generated.resources.and_text import crafto.composeapp.generated.resources.continue_button import crafto.composeapp.generated.resources.egypt_flag import crafto.composeapp.generated.resources.enter_phone +import crafto.composeapp.generated.resources.logo import crafto.composeapp.generated.resources.logo_icon import crafto.composeapp.generated.resources.phone_hint import crafto.composeapp.generated.resources.privacy_agreement import crafto.composeapp.generated.resources.privacy_policy -import crafto.composeapp.generated.resources.register_logo import crafto.composeapp.generated.resources.terms_and_conditions import crafto.composeapp.generated.resources.welcome_title import org.example.project.presentation.designsystem.components.ButtonState @@ -60,6 +67,7 @@ private fun RegisterContent( onTermsClick: () -> Unit, onButtonClick: () -> Unit ) { + var number by remember { mutableStateOf("") } Box( modifier = modifier.fillMaxSize().background(AppTheme.craftoColors.brand.primary) ) { @@ -76,7 +84,7 @@ private fun RegisterContent( ) { Icon( - painter = painterResource(Res.drawable.register_logo), + painter = painterResource(Res.drawable.logo), contentDescription = stringResource(Res.string.logo_icon), modifier = Modifier.align(Alignment.Center).offset(x = (-5).dp, y = (5).dp), tint = AppTheme.craftoColors.brand.primary @@ -112,14 +120,20 @@ private fun RegisterContent( startIcon = { Image( painter = painterResource(Res.drawable.egypt_flag), - contentDescription = stringResource(Res.string.egypt_flag) + contentDescription = stringResource(Res.string.egypt_flag), + modifier = Modifier.padding(start = 18.dp) ) }, showDividerLine = true, maxLines = 1, minLines = 1, - text = "", - onTextChange = {} + showPhoneCode = true, + text = number, + onTextChange = { if (it.length <= 10) number = it }, + inputKeyboard = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Default + ) ) PrivacyAndTextSection(