Repository navigation
[2/3] Onboarding profile picture selection screen #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
70345f9
5a9d900
617b178
996fcf7
a645f2d
f69c41e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| package com.cornellappdev.score.screen | ||
|
|
||
| import androidx.compose.foundation.Image | ||
| import androidx.compose.foundation.background | ||
| import androidx.compose.foundation.layout.Arrangement | ||
| import androidx.compose.foundation.layout.Box | ||
| import androidx.compose.foundation.layout.BoxWithConstraints | ||
| 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.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.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.CrimsonPrimary | ||
| import com.cornellappdev.score.theme.Style | ||
|
|
||
| @Composable | ||
| fun OnboardingProfileSelectScreen( | ||
| onNext: (ProfilePicture) -> Unit, | ||
| modifier: Modifier = Modifier, | ||
| displayName: String? = null | ||
| ) { | ||
| var selectedPicture by rememberSaveable { mutableStateOf(ProfilePicture.TENNIS) } | ||
| val name = displayName ?: stringResource(R.string.onboarding_default_name) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '25,85p' app/src/main/java/com/cornellappdev/score/MainActivity.kt
sed -n '25,115p' app/src/main/java/com/cornellappdev/score/screen/OnboardingProfileScreen.kt
rg -n 'onSignUp|onSignup|displayName|onboarding_default_name' app/src/main/java/com/cornellappdev/score app/src/main/res/values/strings.xmlRepository: cuappdev/score-android Length of output: 11128 🏁 Script executed: git diff --unified=20 539833c91200f0c9a01f30b1733827a0406d967a 996fcf7b57f1d3b6bc698c50b1dca0b2a9972ec1 -- app/src/main/java/com/cornellappdev/score/MainActivity.kt app/src/main/java/com/cornellappdev/score/screen/OnboardingProfileScreen.kt app/src/main/res/values/strings.xmlRepository: cuappdev/score-android Length of output: 11049 Do not show
🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be pushing this? |
||
|
|
||
| BoxWithConstraints( | ||
| modifier = modifier | ||
| .fillMaxSize() | ||
| .background(Color.White) | ||
| ) { | ||
| val designScale = minOf(maxWidth.value / 393f, maxHeight.value / 852f) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come we're doing manual scaling? I'm worried about adaptiveness. |
||
| val designDp: (Float) -> Dp = { (it * designScale).dp } | ||
|
|
||
| Column(modifier = Modifier.fillMaxSize()) { | ||
| Column( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(top = designDp(79f)), | ||
| horizontalAlignment = Alignment.CenterHorizontally | ||
| ) { | ||
| Text( | ||
| text = stringResource(R.string.edit_profile), | ||
| style = Style.heading1, | ||
| color = Color(0xFF333333) | ||
| ) | ||
| Spacer(Modifier.height(designDp(4f))) | ||
| Text( | ||
| text = stringResource(R.string.onboarding_profile_subtitle), | ||
| style = Style.bodyMedium, | ||
| color = Color(0xFF333333) | ||
| ) | ||
|
|
||
| Spacer(Modifier.height(designDp(47f))) | ||
| Image( | ||
| painter = painterResource(selectedPicture.drawable), | ||
| contentDescription = stringResource( | ||
| R.string.selected_profile_picture, | ||
| stringResource(selectedPicture.label) | ||
| ), | ||
| modifier = Modifier.size(designDp(100f)) | ||
| ) | ||
| Spacer(Modifier.height(designDp(16f))) | ||
| Text( | ||
| text = name, | ||
| style = Style.bodyMedium, | ||
| color = Color.Black | ||
| ) | ||
|
|
||
| Spacer(Modifier.height(designDp(60f))) | ||
| 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(designDp(24f))) | ||
| ProfilePictureOptions( | ||
| selectedPicture = selectedPicture, | ||
| onPictureSelected = { selectedPicture = it }, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) | ||
| } | ||
|
|
||
| Spacer(Modifier.weight(1f)) | ||
| OnboardingProfileFooter( | ||
| onNext = { onNext(selectedPicture) }, | ||
| designDp = designDp | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| private fun OnboardingProfileFooter( | ||
| onNext: () -> Unit, | ||
| designDp: (Float) -> Dp | ||
| ) { | ||
| Column( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding( | ||
| start = 24.dp, | ||
| end = 24.dp, | ||
| top = designDp(24f), | ||
| bottom = designDp(60f) | ||
| ), | ||
| horizontalAlignment = Alignment.CenterHorizontally | ||
| ) { | ||
| Button( | ||
| onClick = onNext, | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .height(designDp(48f)), | ||
| shape = CircleShape, | ||
| colors = ButtonDefaults.buttonColors( | ||
| containerColor = CrimsonPrimary, | ||
| contentColor = Color.White | ||
| ) | ||
| ) { | ||
| Text( | ||
| text = stringResource(R.string.next), | ||
| style = Style.heading3, | ||
| color = Color.White | ||
| ) | ||
| } | ||
|
|
||
| Spacer(Modifier.height(designDp(16f))) | ||
| RowProgressDots() | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| private fun RowProgressDots() { | ||
| androidx.compose.foundation.layout.Row( | ||
| horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
| verticalAlignment = Alignment.CenterVertically | ||
| ) { | ||
| Box(Modifier.size(8.dp).background(CrimsonPrimary, CircleShape)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused about this design. Do we ever have the second dot selected?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ya the next button takes the user to a screen to select their sport preferences and the second dot is selected on that screen. |
||
| Box(Modifier.size(8.dp).background(Color(0xFFEAEAEA), CircleShape)) | ||
| } | ||
| } | ||
|
|
||
| @Preview(showBackground = true, widthDp = 393, heightDp = 852) | ||
| @Composable | ||
| private fun OnboardingProfileSelectScreenPreview() { | ||
| OnboardingProfileSelectScreen(onNext = {}) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| 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.layout.widthIn | ||
| 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.tooling.preview.Preview | ||
| 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.unit.dp | ||
| import com.cornellappdev.score.components.ScorePreview | ||
|
|
||
| /** | ||
| * 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 | ||
| .widthIn(max = 393.dp) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, how come we have to do constraint-based sizing? |
||
| .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, Color(0xFF55B63C), CircleShape) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Preview(showBackground = true, widthDp = 393) | ||
| @Composable | ||
| private fun ProfilePictureOptionsPreview() { | ||
| ScorePreview(backgroundColor = Color.White) { | ||
| ProfilePictureOptions( | ||
| selectedPicture = ProfilePicture.TENNIS, | ||
| onPictureSelected = {}, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use enums instead of integers here for readability & safety.