diff --git a/app/src/main/java/com/cornellappdev/score/MainActivity.kt b/app/src/main/java/com/cornellappdev/score/MainActivity.kt index 512793e..bea491f 100644 --- a/app/src/main/java/com/cornellappdev/score/MainActivity.kt +++ b/app/src/main/java/com/cornellappdev/score/MainActivity.kt @@ -9,7 +9,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.core.content.edit import com.cornellappdev.score.nav.root.RootNavigation +import com.cornellappdev.score.screen.FavoriteSport +import com.cornellappdev.score.screen.OnboardingFavoriteSportsScreen +import com.cornellappdev.score.screen.OnboardingProfileSelectScreen import com.cornellappdev.score.screen.OnboardingScreen import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics @@ -18,9 +22,18 @@ import dagger.hilt.android.AndroidEntryPoint private const val APP_PREFERENCES = "score_app_preferences" private const val KEY_ONBOARDING_COMPLETED = "onboarding_completed" +private const val KEY_ONBOARDING_PROFILE_PICTURE = "onboarding_profile_picture" +private const val KEY_ONBOARDING_FAVORITE_SPORTS = "onboarding_favorite_sports" private lateinit var firebaseAnalytics: FirebaseAnalytics +private enum class OnboardingStep { + WELCOME, + PROFILE_SELECT, + SPORT_SELECT, + COMPLETE +} + @AndroidEntryPoint class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -29,29 +42,55 @@ class MainActivity : AppCompatActivity() { val preferences = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE) val onboardingCompleted = preferences.getBoolean(KEY_ONBOARDING_COMPLETED, false) + val savedFavoriteSportKeys = preferences + .getStringSet(KEY_ONBOARDING_FAVORITE_SPORTS, emptySet()) + .orEmpty() + .joinToString("|") setContent { - var showOnboarding by rememberSaveable { - mutableStateOf(!onboardingCompleted) + var onboardingStep by rememberSaveable { + mutableStateOf(if (onboardingCompleted) OnboardingStep.COMPLETE else OnboardingStep.WELCOME) } val completeOnboarding = remember { { - preferences.edit() - .putBoolean(KEY_ONBOARDING_COMPLETED, true) - .apply() - showOnboarding = false + preferences.edit { + putBoolean(KEY_ONBOARDING_COMPLETED, true) + } + onboardingStep = OnboardingStep.COMPLETE } } - if (showOnboarding) { - OnboardingScreen( - onSignUp = completeOnboarding, + when (onboardingStep) { + OnboardingStep.WELCOME -> OnboardingScreen( + onSignUp = { onboardingStep = OnboardingStep.PROFILE_SELECT }, onLogIn = completeOnboarding, onLater = completeOnboarding ) - } else { - RootNavigation() + + OnboardingStep.PROFILE_SELECT -> OnboardingProfileSelectScreen( + onNext = { selectedPicture -> + preferences.edit { + putString(KEY_ONBOARDING_PROFILE_PICTURE, selectedPicture.name) + } + onboardingStep = OnboardingStep.SPORT_SELECT + } + ) + + OnboardingStep.SPORT_SELECT -> OnboardingFavoriteSportsScreen( + initiallySelectedKeys = savedFavoriteSportKeys, + onFinish = { selectedSports -> + preferences.edit { + putStringSet( + KEY_ONBOARDING_FAVORITE_SPORTS, + selectedSports.map(FavoriteSport::storageKey).toSet() + ) + } + completeOnboarding() + } + ) + + OnboardingStep.COMPLETE -> RootNavigation() } } diff --git a/app/src/main/java/com/cornellappdev/score/screen/EditProfileScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/EditProfileScreen.kt index f81b4b0..4c93ff0 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/EditProfileScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/EditProfileScreen.kt @@ -4,23 +4,14 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.Image 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.Spacer -import androidx.compose.foundation.layout.aspectRatio 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.layout.widthIn import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.selection.selectableGroup -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -30,11 +21,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.score.R @@ -89,31 +78,10 @@ fun EditProfileScreen( modifier = Modifier.padding(horizontal = 24.dp) ) Spacer(Modifier.height(24.dp)) - Row( - modifier = Modifier.widthIn(max = 393.dp).fillMaxWidth() - .padding(horizontal = 24.dp).selectableGroup(), - horizontalArrangement = Arrangement.spacedBy(20.dp) - ) { - ProfilePicture.entries.forEach { picture -> - val selected = picture == selectedPicture - Box( - modifier = Modifier.weight(1f).aspectRatio(1f).clip(CircleShape) - .selectable(selected = selected, role = Role.RadioButton, - onClick = { onPictureSelected(picture) }), - contentAlignment = Alignment.Center - ) { - Image( - painter = painterResource(picture.drawable), - contentDescription = stringResource(picture.label), - modifier = Modifier.fillMaxSize() - ) - if (selected) { - Box(Modifier.fillMaxSize().background(Color.White.copy(alpha = 0.6f), CircleShape) - .border(3.dp, Color(0xFF4DBB35), CircleShape)) - } - } - } - } + ProfilePictureOptions( + selectedPicture = selectedPicture, + onPictureSelected = onPictureSelected + ) } } } diff --git a/app/src/main/java/com/cornellappdev/score/screen/OnboardingFavoriteSportsScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/OnboardingFavoriteSportsScreen.kt new file mode 100644 index 0000000..0c8967a --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/OnboardingFavoriteSportsScreen.kt @@ -0,0 +1,371 @@ +package com.cornellappdev.score.screen + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +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.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.R +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.model.GenderDivision +import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.theme.CrimsonPrimary +import com.cornellappdev.score.theme.GrayLight +import com.cornellappdev.score.theme.GrayMedium +import com.cornellappdev.score.theme.GrayPrimary +import com.cornellappdev.score.theme.GrayStroke +import com.cornellappdev.score.theme.GreenWash +import com.cornellappdev.score.theme.Style +import com.cornellappdev.score.theme.White +import com.cornellappdev.score.theme.saturatedGreen + +data class FavoriteSport( + val sport: Sport, + val gender: GenderDivision +) { + val storageKey: String get() = "${sport.name}:${gender.name}" + + companion object { + fun fromStorageKey(key: String): FavoriteSport? { + val parts = key.split(':', limit = 2) + if (parts.size != 2) return null + val sport = runCatching { Sport.valueOf(parts[0]) }.getOrNull() ?: return null + val gender = runCatching { GenderDivision.valueOf(parts[1]) }.getOrNull() + ?: return null + if (gender == GenderDivision.ALL) return null + return FavoriteSport(sport, gender) + } + } +} + +@Composable +fun OnboardingFavoriteSportsScreen( + onFinish: (Set) -> Unit, + modifier: Modifier = Modifier, + initiallySelectedKeys: String = "" +) { + var selectedGender by rememberSaveable { mutableStateOf(GenderDivision.ALL) } + var selectedKeys by rememberSaveable { + mutableStateOf(initiallySelectedKeys) + } + val selectedSports = selectedKeys.split('|').filter(String::isNotBlank).toSet() + val allOptions = rememberFavoriteSportOptions() + val visibleOptions = when (selectedGender) { + GenderDivision.ALL -> allOptions + else -> allOptions.filter { it.gender == selectedGender } + } + + Box( + modifier = modifier + .fillMaxSize() + .background(White) + ) { + Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 79.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.select_favorite_sports), + style = Style.heading1, + color = GrayPrimary, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.favorite_sports_subtitle), + style = Style.bodyMedium, + color = GrayPrimary, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(32.dp)) + FavoriteSportsGenderFilter( + selectedGender = selectedGender, + onGenderSelected = { selectedGender = it } + ) + } + + Spacer(Modifier.height(32.dp)) + LazyVerticalGrid( + columns = GridCells.Fixed(3), + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + contentPadding = PaddingValues( + start = 24.dp, + end = 24.dp, + top = 8.dp, + bottom = 8.dp + ), + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + items(visibleOptions, key = { it.storageKey }) { option -> + FavoriteSportCard( + option = option, + selected = option.storageKey in selectedSports, + onClick = { + selectedKeys = if (option.storageKey in selectedSports) { + selectedSports - option.storageKey + } else { + selectedSports + option.storageKey + }.joinToString("|") + } + ) + } + } + + OnboardingFooter( + buttonText = when (selectedSports.size) { + 0 -> stringResource(R.string.finish) + 1 -> stringResource(R.string.finish_with_one_sport) + else -> stringResource(R.string.finish_with_sports, selectedSports.size) + }, + onClick = { + onFinish(selectedSports.mapNotNull(FavoriteSport::fromStorageKey).toSet()) + }, + activeStep = OnboardingState.SPORT_SELECT + ) + } + } +} + +@Composable +private fun rememberFavoriteSportOptions(): List { + return remember { + Sport.entries + .flatMap { sport -> + when (sport.gender) { + GenderDivision.ALL -> listOf( + FavoriteSport(sport, GenderDivision.MALE), + FavoriteSport(sport, GenderDivision.FEMALE) + ) + + GenderDivision.MALE -> listOf(FavoriteSport(sport, GenderDivision.MALE)) + GenderDivision.FEMALE -> listOf(FavoriteSport(sport, GenderDivision.FEMALE)) + } + } + .sortedWith( + compareBy { it.sport.displayName } + .thenBy { if (it.gender == GenderDivision.MALE) 0 else 1 } + ) + } +} + +@Composable +private fun FavoriteSportsGenderFilter( + selectedGender: GenderDivision, + onGenderSelected: (GenderDivision) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .height(48.dp) + .border( + BorderStroke(1.dp, GrayLight), + CircleShape + ) + .padding(5.dp) + .selectableGroup(), + verticalAlignment = Alignment.CenterVertically + ) { + listOf( + GenderDivision.ALL to R.string.all, + GenderDivision.MALE to R.string.onboarding_mens, + GenderDivision.FEMALE to R.string.onboarding_womens + ).forEach { (gender, label) -> + val selected = gender == selectedGender + Box( + modifier = Modifier + .weight(1f) + .fillMaxSize() + .clip(CircleShape) + .background(if (selected) CrimsonPrimary else Color.Transparent) + .selectable( + selected = selected, + role = Role.RadioButton, + onClick = { onGenderSelected(gender) } + ), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(label), + style = if (selected) Style.bodySemibold else Style.bodyMedium, + color = if (selected) White else GrayMedium + ) + } + } + } +} + +@Composable +private fun FavoriteSportCard( + option: FavoriteSport, + selected: Boolean, + onClick: () -> Unit +) { + val cardShape = RoundedCornerShape(20.dp) + Column( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = selected, + role = Role.Checkbox, + onClick = onClick + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box(modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f)) { + Box( + modifier = Modifier + .fillMaxSize() + .clip(cardShape) + .background(if (selected) GreenWash else GrayStroke) + .border( + width = if (selected) 3.dp else 1.dp, + color = if (selected) saturatedGreen else GrayStroke, + shape = cardShape + ), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(option.sport.emptyIcon), + contentDescription = null, + modifier = Modifier.size(45.dp), + colorFilter = ColorFilter.tint( + if (selected) saturatedGreen else GrayPrimary + ) + ) + } + + if (selected) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .offset(x = (-8).dp, y = (-8).dp) + .size(32.dp) + .background(CrimsonPrimary, CircleShape), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(R.drawable.ic_round_minus), + contentDescription = stringResource(R.string.remove_favorite_sport), + modifier = Modifier.size(24.dp), + colorFilter = ColorFilter.tint(White) + ) + } + } + } + + Spacer(Modifier.height(8.dp)) + Text( + text = option.sport.displayName, + style = Style.bodyMedium, + color = GrayPrimary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(2.dp)) + FavoriteSportGenderTag(option.gender) + } +} + +@Composable +private fun FavoriteSportGenderTag( + gender: GenderDivision, +) { + val label = if (gender == GenderDivision.FEMALE) "W" else "M" + val genderIcon = if (gender == GenderDivision.FEMALE) { + R.drawable.ic_light_gray_female_gender + } else { + R.drawable.ic_light_gray_male_gender + } + + Row( + modifier = Modifier + .background(White, RoundedCornerShape(100)) + .padding(horizontal = 2.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + Box( + modifier = Modifier + .size(18.dp) + .background(GrayStroke, CircleShape), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(genderIcon), + contentDescription = null, + modifier = Modifier.size(12.dp) + ) + } + Text( + text = label, + style = Style.labelsNormal, + color = GrayPrimary + ) + } +} + + +@Preview +@Composable +private fun OnboardingFavoriteSportsScreenPreview() { + ScorePreview { + OnboardingFavoriteSportsScreen(onFinish = {}) + } +} + +@Preview +@Composable +private fun OnboardingFavoriteSportsSelectedScreenPreview() { + ScorePreview { + OnboardingFavoriteSportsScreen( + onFinish = {}, + initiallySelectedKeys = "BASEBALL:MALE|CROSS_COUNTRY:FEMALE" + ) + } +} diff --git a/app/src/main/java/com/cornellappdev/score/screen/OnboardingFooter.kt b/app/src/main/java/com/cornellappdev/score/screen/OnboardingFooter.kt new file mode 100644 index 0000000..4af4f06 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/OnboardingFooter.kt @@ -0,0 +1,94 @@ +package com.cornellappdev.score.screen + +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.Spacer +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.CircleShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.theme.CrimsonPrimary +import com.cornellappdev.score.theme.GrayStroke +import com.cornellappdev.score.theme.Style +import com.cornellappdev.score.theme.White + +enum class OnboardingState{ + PROFILE_SELECT, SPORT_SELECT +} + +@Composable +fun OnboardingFooter( + buttonText: String, + onClick: () -> Unit, + activeStep: OnboardingState +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(White) + .padding( + start = 24.dp, + end = 24.dp, + top = 24.dp, + bottom = 60.dp + ), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + shape = CircleShape, + colors = ButtonDefaults.buttonColors( + containerColor = CrimsonPrimary, + contentColor = White + ) + ) { + Text(buttonText, style = Style.heading3, color = White) + } + + Spacer(Modifier.height(16.dp)) + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + repeat(2) { index -> + val isActive = when (activeStep) { + OnboardingState.PROFILE_SELECT -> index == 0 + OnboardingState.SPORT_SELECT -> index == 1 + } + Box( + Modifier + .size(8.dp) + .background(if (isActive) CrimsonPrimary else GrayStroke, CircleShape) + ) + } + } + } +} + +@Preview +@Composable +private fun OnboardingFooterPreview() { + ScorePreview { + OnboardingFooter( + buttonText = "Next", + onClick = {}, + activeStep = OnboardingState.PROFILE_SELECT + ) + } +} diff --git a/app/src/main/java/com/cornellappdev/score/screen/OnboardingProfileSelectScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/OnboardingProfileSelectScreen.kt new file mode 100644 index 0000000..5a52426 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/OnboardingProfileSelectScreen.kt @@ -0,0 +1,113 @@ +package com.cornellappdev.score.screen + +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.Spacer +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +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.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.R +import com.cornellappdev.score.theme.GrayPrimary +import com.cornellappdev.score.theme.Style + +@Composable +fun OnboardingProfileSelectScreen( + onNext: (ProfilePicture) -> Unit, + modifier: Modifier = Modifier, + displayName: String? = null +) { + var selectedPicture by rememberSaveable { mutableStateOf(ProfilePicture.BLANK) } + val name = displayName ?: stringResource(R.string.onboarding_default_name) + + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + ) { + Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 80.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.edit_profile), + style = Style.heading1, + color = GrayPrimary + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.onboarding_profile_subtitle), + style = Style.bodyMedium, + color = GrayPrimary + ) + + Spacer(Modifier.height(40.dp)) + Image( + painter = painterResource(selectedPicture.drawable), + contentDescription = stringResource( + R.string.selected_profile_picture, + stringResource(selectedPicture.label) + ), + modifier = Modifier.size(100.dp) + ) + Spacer(Modifier.height(16.dp)) + Text( + text = name, + style = Style.bodyMedium, + color = Color.Black + ) + + Spacer(Modifier.height(60.dp)) + Text( + text = stringResource(R.string.onboarding_select_profile_picture), + style = Style.metricMedium, + color = Color.Black, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) + Spacer(Modifier.height(24.dp)) + ProfilePictureOptions( + selectedPicture = selectedPicture, + onPictureSelected = { selectedPicture = it }, + modifier = Modifier.fillMaxWidth() + ) + } + + Spacer(Modifier.weight(1f)) + OnboardingFooter( + buttonText = stringResource(R.string.next), + onClick = { onNext(selectedPicture) }, + activeStep = OnboardingState.PROFILE_SELECT + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun OnboardingProfileSelectScreenPreview() { + OnboardingProfileSelectScreen(onNext = {}) +} diff --git a/app/src/main/java/com/cornellappdev/score/screen/OnboardingScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/OnboardingScreen.kt index aeaba8c..368a0f6 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/OnboardingScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/OnboardingScreen.kt @@ -12,10 +12,13 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -415,6 +418,7 @@ fun OnboardingScreen( onLater = onLater, modifier = Modifier .align(Alignment.BottomCenter) + .windowInsetsPadding(WindowInsets.navigationBars) .padding(start = 24.dp, end = 24.dp, bottom = 40.dp) ) } diff --git a/app/src/main/java/com/cornellappdev/score/screen/ProfilePictureOptions.kt b/app/src/main/java/com/cornellappdev/score/screen/ProfilePictureOptions.kt new file mode 100644 index 0000000..dcae5c4 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/ProfilePictureOptions.kt @@ -0,0 +1,86 @@ +package com.cornellappdev.score.screen + +import androidx.compose.foundation.Image +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.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.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.theme.saturatedGreen + +/** + * Shared profile picture choices used by profile editing and first-time onboarding. + */ +@Composable +fun ProfilePictureOptions( + selectedPicture: ProfilePicture, + onPictureSelected: (ProfilePicture) -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .selectableGroup(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + ProfilePicture.entries.forEach { picture -> + val selected = picture == selectedPicture + Box( + modifier = Modifier + .size(70.dp) + .clip(CircleShape) + .selectable( + selected = selected, + role = Role.RadioButton, + onClick = { onPictureSelected(picture) } + ), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(picture.drawable), + contentDescription = stringResource(picture.label), + modifier = Modifier.fillMaxSize() + ) + if (selected) { + Box( + Modifier + .fillMaxSize() + .background(Color.White.copy(alpha = 0.6f), CircleShape) + .border(3.dp, saturatedGreen, CircleShape) + ) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ProfilePictureOptionsPreview() { + ScorePreview(backgroundColor = Color.White) { + ProfilePictureOptions( + selectedPicture = ProfilePicture.TENNIS, + onPictureSelected = {}, + modifier = Modifier.fillMaxWidth() + ) + } +} diff --git a/app/src/main/java/com/cornellappdev/score/theme/Color.kt b/app/src/main/java/com/cornellappdev/score/theme/Color.kt index a4f8fa5..799aaee 100644 --- a/app/src/main/java/com/cornellappdev/score/theme/Color.kt +++ b/app/src/main/java/com/cornellappdev/score/theme/Color.kt @@ -11,6 +11,7 @@ val GrayStroke = Color(0xFFEAEAEA) val GreenAccent = Color(0xFF247211) val RedAccent = Color(0xFFC41616) val GreenLight = Color(0xFF88D598) +val GreenWash = Color(0xFFF6FFF3) val BlueLight = Color(0XFF5AA8D5) val CrimsonPrimary = Color(0xFFA5210D) val PeachPrimary = Color(0xFFE1A69F) diff --git a/app/src/main/res/drawable/ic_light_gray_female_gender.xml b/app/src/main/res/drawable/ic_light_gray_female_gender.xml new file mode 100644 index 0000000..81e066a --- /dev/null +++ b/app/src/main/res/drawable/ic_light_gray_female_gender.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_light_gray_male_gender.xml b/app/src/main/res/drawable/ic_light_gray_male_gender.xml new file mode 100644 index 0000000..3039fdb --- /dev/null +++ b/app/src/main/res/drawable/ic_light_gray_male_gender.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c264cc3..75e707e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,17 @@ Tennis Baseball Soccer + Your Name + You can change this later in the profile page. + Select profile picture: + Next + Select Favorite Sports + You can change your picks at any time! + All + Men’s + Women’s + Finish + Finish with %1$d sports + Finish with 1 sport + Remove favorite sport \ No newline at end of file