diff --git a/composeApp/src/commonMain/composeResources/drawable/Onboarding.png b/composeApp/src/commonMain/composeResources/drawable/Onboarding.png new file mode 100644 index 000000000..7d9e2dfa8 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/Onboarding.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/arrow-down.png b/composeApp/src/commonMain/composeResources/drawable/arrow-down.png new file mode 100644 index 000000000..568bfce77 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/arrow-down.png differ diff --git a/composeApp/src/commonMain/composeResources/values-ar/strings.xml b/composeApp/src/commonMain/composeResources/values-ar/strings.xml index 06dc5f424..6cb57184a 100644 --- a/composeApp/src/commonMain/composeResources/values-ar/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-ar/strings.xml @@ -37,4 +37,12 @@ كجم قدم رطل + مستعد للبدء + هل لديك حساب؟ + تسجيل الدخول + اختر اللغة + سيتم استخدام هذه اللغة في جميع أنحاء التطبيق. + العربية + الإنجليزية + تأكيد \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index d455de279..a67f48578 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -34,6 +34,15 @@ Meal Image Calories Icon Kcal + Ready to Start + Do you have an account? + Login + Choose Language + This language will be used throughout the app. + Arabic + English + Confirm + cm kg ft diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/di/viewModelModule.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/di/viewModelModule.kt index 22d162a76..60a196384 100644 --- a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/di/viewModelModule.kt +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/di/viewModelModule.kt @@ -1,9 +1,11 @@ package com.cairosquad.evolvefit.di +import com.cairosquad.evolvefit.ui.screen.onBoarding.OnBoardingViewModel import com.cairosquad.evolvefit.viewmodel.register.RegisterViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val viewModelModule = module { viewModelOf(::RegisterViewModel) + viewModelOf(::OnBoardingViewModel) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnBoardingViewModel.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnBoardingViewModel.kt new file mode 100644 index 000000000..68b03aba8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnBoardingViewModel.kt @@ -0,0 +1,37 @@ +package com.cairosquad.evolvefit.ui.screen.onBoarding + +import com.cairosquad.evolvefit.viewmodel.base.BaseViewModel + +class OnBoardingViewModel : + BaseViewModel(OnboardingScreenState()), + OnboardingScreenListener { + + override fun onChangeLanguage(language: OnboardingScreenState.Language) { + updateState { + it.copy(bottomSheetSelectedLanguage = language) + } + } + + override fun onConfirmClicked() { + updateState { + it.copy( + selectedLanguage = it.bottomSheetSelectedLanguage, + isBottomSheetOpen = false + ) + } + } + + override fun toggleBottomSheet(isOpen: Boolean) { + updateState { + it.copy(isBottomSheetOpen = isOpen) + } + } + + override fun onSignUpClicked() { + sendEffect(OnboardingScreenEffect.NavigateToRegister) + } + + override fun onLoginClicked() { + sendEffect(OnboardingScreenEffect.NavigateToLogin) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreen.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreen.kt index d7f51de44..d6e91fd04 100644 --- a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreen.kt @@ -1,39 +1,222 @@ package com.cairosquad.evolvefit.ui.screen.onBoarding +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Button +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.layout.statusBarsPadding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import com.cairosquad.evolvefit.design_system.component.BottomSheet +import com.cairosquad.evolvefit.design_system.component.CheckboxItem +import com.cairosquad.evolvefit.design_system.component.CheckboxStyle +import com.cairosquad.evolvefit.design_system.component.PrimaryButton +import com.cairosquad.evolvefit.design_system.theme.AppTheme import com.cairosquad.evolvefit.design_system.theme.Theme +import com.cairosquad.evolvefit.ui.navigation.LoginRoute +import com.cairosquad.evolvefit.ui.navigation.RegisterRoute +import com.cairosquad.evolvefit.ui.util.ObserveAsEffect +import com.cairosquad.evolvefit.ui.util.noRippleClickable +import org.jetbrains.compose.ui.tooling.preview.Preview +import evolvefit.composeapp.generated.resources.Onboarding +import evolvefit.composeapp.generated.resources.Res +import evolvefit.composeapp.generated.resources.arrow_down +import evolvefit.composeapp.generated.resources.choose_language +import evolvefit.composeapp.generated.resources.do_you_have_an_account +import evolvefit.composeapp.generated.resources.language_selection_description +import evolvefit.composeapp.generated.resources.login +import evolvefit.composeapp.generated.resources.ready_to_start +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel @Composable fun OnboardingScreen( - navigateToLogin: () -> Unit, - navigateToRegister: () -> Unit, + viewmodel: OnBoardingViewModel = koinViewModel(), + navigateToRegister : () -> Unit, + navigateToLogin : () -> Unit, ) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally + val state by viewmodel.screenState.collectAsStateWithLifecycle() + ObserveAsEffect(viewmodel.effect) { effect -> + when (effect) { + OnboardingScreenEffect.NavigateToLogin -> { + navigateToLogin() + } + OnboardingScreenEffect.NavigateToRegister -> { + navigateToRegister() + } + } + } + OnboardingScreenContent( + state = state, + onSignupClick = viewmodel::onSignUpClicked, + onLoginClick = viewmodel::onLoginClicked, + onToggleBottomSheet = viewmodel::toggleBottomSheet, + onLanguageSelected = viewmodel::onChangeLanguage, + onConfirmClick = viewmodel::onConfirmClicked + ) +} + +@Composable +fun OnboardingScreenContent( + state: OnboardingScreenState, + modifier: Modifier = Modifier, + onSignupClick: () -> Unit, + onLoginClick: () -> Unit, + onToggleBottomSheet: (isOpen: Boolean) -> Unit, + onLanguageSelected: (language: OnboardingScreenState.Language) -> Unit, + onConfirmClick: () -> Unit +) { + Box( + modifier = modifier.fillMaxSize() ) { + Image( + painter = painterResource(Res.drawable.Onboarding), + contentDescription = "onboarding image", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.FillBounds + ) + Row( + modifier = Modifier.align(Alignment.TopEnd).statusBarsPadding().height(48.dp) + .wrapContentWidth() + .noRippleClickable { onToggleBottomSheet(state.isBottomSheetOpen.not()) }, + horizontalArrangement = Arrangement.End + ) { + Text( + text = stringResource(state.selectedLanguage.displayNameRes), + style = Theme.textStyle.label.mediumMedium16, + color = Theme.color.surfaces.textColor, + modifier = Modifier.padding(top = 14.5.dp, bottom = 14.5.dp, end = 8.dp) + ) + Image( + painter = painterResource(Res.drawable.arrow_down), + contentDescription = "arrow down", + modifier = Modifier.padding( + end = 16.dp, top = 14.5.dp, bottom = 14.5.dp + ).size(20.dp) + ) + } + //bottom section [ready to start - login] + Column( + modifier = Modifier.align(Alignment.BottomCenter), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 24.dp) + .clip(RoundedCornerShape(24.dp)) + .height(48.dp).background( + Theme.color.brand.primary, + ).fillMaxWidth().noRippleClickable(onClick = onSignupClick), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(Res.string.ready_to_start), + style = Theme.textStyle.body.mediumMedium14, + color = Theme.color.brand.onPrimary + ) + } - Text("Welcome to EvolveFit", color = Theme.color.surfaces.onSurface) - Text("Get Fit Don't Quit", color = Theme.color.surfaces.onSurface) + Row( + modifier = Modifier.padding(bottom = 24.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(Res.string.do_you_have_an_account), + style = Theme.textStyle.label.mediumMedium14, + color = Theme.color.surfaces.textColor, + modifier = Modifier.padding(end = 4.dp) + ) + Text( + text = stringResource(Res.string.login), + style = Theme.textStyle.label.mediumMedium16, + color = Theme.color.brand.primary, + modifier = Modifier.padding(end = 4.dp).noRippleClickable(onClick = onLoginClick) + ) + + } - Button( - onClick = navigateToLogin - ){ - Text("go to Login") - } - Button( - onClick = navigateToRegister - ){ - Text("go to Register") } + + BottomSheet( + isVisible = state.isBottomSheetOpen, + onDismiss = { + onToggleBottomSheet(false) + }, + content = { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = stringResource(Res.string.choose_language), + style = Theme.textStyle.label.mediumMedium16, + color = Theme.color.surfaces.onSurface, + modifier = Modifier.padding(bottom = 4.dp) + ) + Text( + text = stringResource(Res.string.language_selection_description), + style = Theme.textStyle.label.mediumMedium12, + color = Theme.color.surfaces.onSurfaceVariant, + modifier = Modifier.padding(bottom = 16.dp) + ) + OnboardingScreenState.Language.entries.forEach { language -> + CheckboxItem( + text = stringResource(language.displayNameRes), + isChecked = language == state.bottomSheetSelectedLanguage, + onCheckedChange = { onLanguageSelected(language) }, + style = CheckboxStyle.Tick, + modifier = modifier.padding( + bottom = 12.dp + ) + ) + } + PrimaryButton( + text = "Confirm", + onClick = { onConfirmClick() }, + modifier = Modifier.padding( + start = 16.dp, end = 16.dp, bottom = 16.dp , top = 28.dp + ) + ) + } + }, + ) + } -} \ No newline at end of file +} + +@Preview +@Composable +fun OnboardingScreenContentPreview() { + var state = OnboardingScreenState().copy(isBottomSheetOpen = true,) + AppTheme( + isDarkTheme = true + ) { + OnboardingScreenContent( + state, + onSignupClick = { }, + onLoginClick = { }, + onToggleBottomSheet = {state.isBottomSheetOpen.not()}, + onLanguageSelected = { }, + onConfirmClick = {} + ) + } + +} diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenEffect.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenEffect.kt new file mode 100644 index 000000000..9f2d608ab --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenEffect.kt @@ -0,0 +1,6 @@ +package com.cairosquad.evolvefit.ui.screen.onBoarding + +sealed class OnboardingScreenEffect { + object NavigateToLogin : OnboardingScreenEffect() + object NavigateToRegister : OnboardingScreenEffect() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenListener.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenListener.kt new file mode 100644 index 000000000..32457af83 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenListener.kt @@ -0,0 +1,9 @@ +package com.cairosquad.evolvefit.ui.screen.onBoarding + +interface OnboardingScreenListener { + fun onChangeLanguage(language: OnboardingScreenState.Language) + fun toggleBottomSheet(isOpen: Boolean) + fun onSignUpClicked() + fun onLoginClicked() + fun onConfirmClicked() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenState.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenState.kt new file mode 100644 index 000000000..166bc8dfc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/screen/onBoarding/OnboardingScreenState.kt @@ -0,0 +1,17 @@ +package com.cairosquad.evolvefit.ui.screen.onBoarding + +import evolvefit.composeapp.generated.resources.Res +import evolvefit.composeapp.generated.resources.arabic +import evolvefit.composeapp.generated.resources.english +import org.jetbrains.compose.resources.StringResource + +data class OnboardingScreenState( + val selectedLanguage: Language = Language.ENGLISH, + val isBottomSheetOpen: Boolean = false, + val bottomSheetSelectedLanguage : Language = Language.ENGLISH +) { + enum class Language( val displayNameRes: StringResource) { + ARABIC(Res.string.arabic), + ENGLISH(Res.string.english) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/util/NoRippleClick.kt b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/util/NoRippleClick.kt new file mode 100644 index 000000000..4bb3c888a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/cairosquad/evolvefit/ui/util/NoRippleClick.kt @@ -0,0 +1,15 @@ +package com.cairosquad.evolvefit.ui.util + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed + +fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed { + this.clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }) { + onClick() + } +}