diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b9c07384..bb29ccca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" + android:screenOrientation="portrait" android:supportsRtl="true" android:theme="@style/Theme.Ilab" tools:targetApi="31"> diff --git a/build-logic/src/main/kotlin/com/nexters/ilab/android/Compose.kt b/build-logic/src/main/kotlin/com/nexters/ilab/android/Compose.kt index f4a687ae..e5322f77 100644 --- a/build-logic/src/main/kotlin/com/nexters/ilab/android/Compose.kt +++ b/build-logic/src/main/kotlin/com/nexters/ilab/android/Compose.kt @@ -19,6 +19,7 @@ internal fun Project.configureCompose(extension: CommonExtension<*, *, *, *, *>) dependencies { implementation(libs.androidx.compose.bom) androidTestImplementation(libs.androidx.compose.bom) + implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.tooling.preview) diff --git a/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Color.kt b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Color.kt index e2885390..81e87a54 100644 --- a/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Color.kt +++ b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Color.kt @@ -9,3 +9,36 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) + +val PurpleBlue100 = Color(0xFFF1F5FF) +val PurpleBlue200 = Color(0xFFDCE6FE) +val PurpleBlue300 = Color(0xFFB9CEFD) +val PurpleBlue400 = Color(0xFF97B5FD) +val PurpleBlue500 = Color(0xFF749DFC) +val PurpleBlue600 = Color(0xFF5A8BFF) +val PurpleBlue700 = Color(0xFF346AE9) +val PurpleBlue800 = Color(0xFF2248AC) +val PurpleBlue900 = Color(0xFF0B235C) + +val Blue100 = Color(0xFFEEFAFF) +val Blue200 = Color(0xFFDAF4FF) +val Blue300 = Color(0xFFA0E2FF) +val Blue400 = Color(0xFF61CCF6) +val Blue500 = Color(0xFF0AB7FF) +val Blue600 = Color(0xFF0791E0) +val Blue700 = Color(0xFF0079C9) +val Blue800 = Color(0xFF0066AF) +val Blue900 = Color(0xFF043A6F) + +val Gray100 = Color(0xFFF2F3F6) +val Gray200 = Color(0xFFE3E5E9) +val Gray300 = Color(0xFFD7D8DA) +val Gray400 = Color(0xFFBDBEC0) +val Gray500 = Color(0xFF747479) +val Gray600 = Color(0xFF424243) +val Gray700 = Color(0xFF2B2B2B) +val Gray800 = Color(0xFF1C1C1C) +val Gray900 = Color(0xFF121212) + +val SystemGreen = Color(0xFF4FCF6B) +val SystemRed = Color(0xFFFF584E) diff --git a/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Font.kt b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Font.kt new file mode 100644 index 00000000..3856360a --- /dev/null +++ b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Font.kt @@ -0,0 +1,65 @@ +package com.nexters.ilab.android.core.designsystem.theme + +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.nexters.ilab.android.core.designsystem.R + +val pretendardFamily = FontFamily( + Font(R.font.pretendard_bold, FontWeight.Bold, FontStyle.Normal), + Font(R.font.pretendard_semi_bold, FontWeight.SemiBold, FontStyle.Normal), + Font(R.font.pretendard_medium, FontWeight.Medium, FontStyle.Normal), + Font(R.font.pretendard_regular, FontWeight.Normal, FontStyle.Normal), +) + +val Title1 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.Bold, + fontSize = 28.sp, + lineHeight = 39.sp, +) + +val Title2 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 20.sp, + lineHeight = 28.sp, +) + +val Subtitle1 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp, + lineHeight = 25.sp, +) + +val Subtitle2 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = 22.sp, +) + +val Contents1 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp, +) + +val Contents2 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 22.sp, +) + +val Contents3 = TextStyle( + fontFamily = pretendardFamily, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + lineHeight = 18.sp, +) diff --git a/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Theme.kt index 7a16b84b..45c85494 100644 --- a/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Theme.kt +++ b/core/designsystem/src/main/kotlin/com/nexters/ilab/android/core/designsystem/theme/Theme.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -53,11 +54,24 @@ fun ILabTheme( else -> LightColorScheme } val view = LocalView.current + if (!view.isInEditMode) { SideEffect { val window = (view.context as Activity).window - window.statusBarColor = colorScheme.primary.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + + window.statusBarColor = if (darkTheme) Color.Black.toArgb() else Color.White.toArgb() + window.navigationBarColor = if (darkTheme) Color.Black.toArgb() else Color.White.toArgb() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // remove unnecessary black screen from bars + window.isNavigationBarContrastEnforced = false + } + + val windowsInsetsController = WindowCompat.getInsetsController(window, view) + + // status bar's icon always visible + windowsInsetsController.isAppearanceLightStatusBars = !darkTheme + windowsInsetsController.isAppearanceLightNavigationBars = !darkTheme } } diff --git a/core/designsystem/src/main/res/drawable/ic_arrow_back.xml b/core/designsystem/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 00000000..ad17abb5 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_bad_example1.png b/core/designsystem/src/main/res/drawable/ic_bad_example1.png new file mode 100644 index 00000000..095d1633 Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_bad_example1.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_bad_example2.png b/core/designsystem/src/main/res/drawable/ic_bad_example2.png new file mode 100644 index 00000000..c8977242 Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_bad_example2.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_bad_example3.png b/core/designsystem/src/main/res/drawable/ic_bad_example3.png new file mode 100644 index 00000000..a6f539b1 Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_bad_example3.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_close.xml b/core/designsystem/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..47b98d15 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_close.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_good_example1.png b/core/designsystem/src/main/res/drawable/ic_good_example1.png new file mode 100644 index 00000000..a110832a Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_good_example1.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_good_example2.png b/core/designsystem/src/main/res/drawable/ic_good_example2.png new file mode 100644 index 00000000..15a1ca7a Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_good_example2.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_good_example3.png b/core/designsystem/src/main/res/drawable/ic_good_example3.png new file mode 100644 index 00000000..7dec76f4 Binary files /dev/null and b/core/designsystem/src/main/res/drawable/ic_good_example3.png differ diff --git a/core/designsystem/src/main/res/drawable/ic_guide_error.xml b/core/designsystem/src/main/res/drawable/ic_guide_error.xml new file mode 100644 index 00000000..883541c8 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_guide_error.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_guide_right.xml b/core/designsystem/src/main/res/drawable/ic_guide_right.xml new file mode 100644 index 00000000..7ec2f26a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_guide_right.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/designsystem/src/main/res/font/pretendard_bold.otf b/core/designsystem/src/main/res/font/pretendard_bold.otf new file mode 100644 index 00000000..e6d6ce88 Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_bold.otf differ diff --git a/core/designsystem/src/main/res/font/pretendard_medium.otf b/core/designsystem/src/main/res/font/pretendard_medium.otf new file mode 100644 index 00000000..ff907c42 Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_medium.otf differ diff --git a/core/designsystem/src/main/res/font/pretendard_regular.otf b/core/designsystem/src/main/res/font/pretendard_regular.otf new file mode 100644 index 00000000..858cdd30 Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_regular.otf differ diff --git a/core/designsystem/src/main/res/font/pretendard_semi_bold.otf b/core/designsystem/src/main/res/font/pretendard_semi_bold.otf new file mode 100644 index 00000000..fe81db7d Binary files /dev/null and b/core/designsystem/src/main/res/font/pretendard_semi_bold.otf differ diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index f4587d80..a20c250e 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -5,4 +5,25 @@ 마이페이지 네트워크 연결이 원활하지 않습니다. 알 수 없는 오류가 발생하였습니다. + + + 사진 가이드 + 가이드 확인 + 사진 찍기 + 사진 보관함 + 사진 바꾸기 + 확인 + GOOD +  좋은 예시 + - 밝고 선명한 얼굴, 카메라를 보고 있는 사진\n- 내가 제일 잘나왔다고 생각하는 사진 + NORMAL + BAD +  좋지 않은 예시 + - 어둡거나 흐린 얼굴\n- 얼굴을 가린 사진(안경, 마스크, 모자 등) + 다시 한번\n확인해주세요! + 정확한 결과를 위해 확인이 필요합니다. + 밝고 선명한 얼굴, 카메라 보고 있는 사진을 선택해주세요. + 내가 제일 잘나왔다고 생각하는 사진을 선택해주세요. + 어둡거나 흐린 사진은 피해주세요. + 안경이나 마스크, 모자 등 얼굴을 가린 사진은 피해주세요. diff --git a/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Button.kt b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Button.kt new file mode 100644 index 00000000..23cfe103 --- /dev/null +++ b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Button.kt @@ -0,0 +1,135 @@ +package com.nexters.ilab.core.ui.component + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.nexters.ilab.android.core.designsystem.theme.Gray300 +import com.nexters.ilab.android.core.designsystem.theme.PurpleBlue600 +import com.nexters.ilab.core.ui.ComponentPreview + +// leadingIcon for login button +@Composable +fun ILabButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + containerColor: Color = PurpleBlue600, + contentColor: Color = Color.White, + disabledContainerColor: Color = Gray300, + disabledContentColor: Color = Color.White, + text: @Composable () -> Unit, + leadingIcon: @Composable (() -> Unit)? = null, +) { + ILabButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + containerColor = containerColor, + contentColor = contentColor, + disabledContainerColor = disabledContainerColor, + disabledContentColor = disabledContentColor, + contentPadding = if (leadingIcon != null) { + ButtonDefaults.ButtonWithIconContentPadding + } else { + ButtonDefaults.ContentPadding + }, + ) { + ILabButtonContent( + text = text, + leadingIcon = leadingIcon, + ) + } +} + +@Composable +fun ILabButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + containerColor: Color = PurpleBlue600, + contentColor: Color = Color.White, + disabledContainerColor: Color = Gray300, + disabledContentColor: Color = Color.White, + contentPadding: PaddingValues = ButtonDefaults.ContentPadding, + content: @Composable RowScope.() -> Unit, +) { + Button( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = containerColor, + contentColor = contentColor, + disabledContainerColor = disabledContainerColor, + disabledContentColor = disabledContentColor, + ), + contentPadding = contentPadding, + content = content, + ) +} + +@Composable +private fun ILabButtonContent( + text: @Composable () -> Unit, + leadingIcon: @Composable (() -> Unit)? = null, +) { + if (leadingIcon != null) { + Box(Modifier.sizeIn(maxHeight = ButtonDefaults.IconSize)) { + leadingIcon() + } + } + Box( + Modifier.padding( + start = if (leadingIcon != null) { + ButtonDefaults.IconSpacing + } else { + 0.dp + }, + ), + ) { + text() + } +} + +@ComponentPreview +@Composable +fun ILabButtonPreview() { + ILabButton( + onClick = {}, + text = { + Text("Button") + }, + ) +} + +@ComponentPreview +@Composable +fun ILabButtonWithLeadingIconPreview() { + ILabButton( + onClick = {}, + text = { + Text("Button") + }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = "Navigation icon", + tint = Color.White, + ) + }, + ) +} diff --git a/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Image.kt b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Image.kt new file mode 100644 index 00000000..7730f2e0 --- /dev/null +++ b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/Image.kt @@ -0,0 +1,93 @@ +package com.nexters.ilab.core.ui.component + +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.nexters.ilab.core.ui.ComponentPreview + +@Composable +fun ExampleImage( + resId: Int, + contentDescription: String, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + + if (LocalInspectionMode.current) { + Icon( + imageVector = Icons.Outlined.Person, + contentDescription = "Image Icon", + modifier = Modifier + .width(103.dp) + .height(139.dp), + ) + } else { + AsyncImage( + model = ImageRequest.Builder(context) + .data(resId) + .crossfade(true) + .build(), + contentDescription = contentDescription, + contentScale = ContentScale.Fit, + modifier = modifier, + ) + } +} + +@Composable +fun NetworkImage( + imageUrl: String, + contentDescription: String, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + + if (LocalInspectionMode.current) { + Icon( + imageVector = Icons.Outlined.Person, + contentDescription = "Image Icon", + modifier = Modifier + .width(186.dp) + .aspectRatio(1f), + ) + } else { + AsyncImage( + model = ImageRequest.Builder(context) + .data(imageUrl) + .crossfade(true) + .build(), + contentDescription = contentDescription, + contentScale = ContentScale.Fit, + modifier = modifier, + ) + } +} + +@ComponentPreview +@Composable +fun ExampleImagePreview() { + ExampleImage( + resId = 0, + contentDescription = "Image Icon", + ) +} + +@ComponentPreview +@Composable +fun NetworkImagePreview() { + NetworkImage( + imageUrl = "", + contentDescription = "Image Icon", + ) +} diff --git a/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/TopAppBar.kt b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/TopAppBar.kt new file mode 100644 index 00000000..49a449df --- /dev/null +++ b/core/ui/src/main/kotlin/com/nexters/ilab/core/ui/component/TopAppBar.kt @@ -0,0 +1,99 @@ +package com.nexters.ilab.core.ui.component + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.nexters.ilab.android.core.designsystem.R +import com.nexters.ilab.android.core.designsystem.theme.Subtitle1 +import com.nexters.ilab.core.ui.ComponentPreview + +@Composable +fun ILabTopAppBar( + @StringRes titleRes: Int, + navigationType: TopAppBarNavigationType, + navigationIconContentDescription: String?, + modifier: Modifier = Modifier, + contentColor: Color = Color.Black, + containerColor: Color = Color.White, + onNavigationClick: () -> Unit = {}, +) { + CompositionLocalProvider(LocalContentColor provides contentColor) { + val icon: @Composable (Modifier, imageVector: ImageVector) -> Unit = + { modifier, imageVector -> + IconButton( + onClick = onNavigationClick, + modifier = modifier.size(48.dp), + ) { + Icon( + imageVector = imageVector, + contentDescription = navigationIconContentDescription, + ) + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .background(containerColor) + .then(modifier), + ) { + if (navigationType == TopAppBarNavigationType.Back) { + icon( + Modifier.align(Alignment.CenterStart), + ImageVector.vectorResource(id = R.drawable.ic_arrow_back), + ) + } + + if (navigationType == TopAppBarNavigationType.Close) { + icon( + Modifier.align(Alignment.CenterEnd), + ImageVector.vectorResource(id = R.drawable.ic_close), + ) + } + + Text( + text = stringResource(id = titleRes), + modifier = Modifier.align(Alignment.Center), + style = Subtitle1, + color = Color.Black, + ) + } + } +} + +enum class TopAppBarNavigationType { Back, Close } + +@ComponentPreview +@Composable +fun ILabTopAppBarBackPreview() { + ILabTopAppBar( + titleRes = android.R.string.untitled, + navigationType = TopAppBarNavigationType.Back, + navigationIconContentDescription = "Navigation back icon", + ) +} + +@ComponentPreview +@Composable +fun ILabTopAppBarClosePreview() { + ILabTopAppBar( + titleRes = android.R.string.untitled, + navigationType = TopAppBarNavigationType.Close, + navigationIconContentDescription = "Navigation close icon", + ) +} diff --git a/feature/camera/build.gradle.kts b/feature/camera/build.gradle.kts index 83bb0632..bf7e8b4c 100644 --- a/feature/camera/build.gradle.kts +++ b/feature/camera/build.gradle.kts @@ -10,6 +10,7 @@ android { dependencies { implementations( + libs.kotlinx.collections.immutable, libs.androidx.core, libs.timber, ) diff --git a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/CameraScreen.kt b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/CameraScreen.kt deleted file mode 100644 index f6899044..00000000 --- a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/CameraScreen.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.nexters.ilab.android.feature.camera - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel - -@Suppress("unused") -@Composable -internal fun CameraRoute( - padding: PaddingValues, - onShowErrorSnackBar: (throwable: Throwable?) -> Unit, - viewModel: CameraViewModel = hiltViewModel(), -) { - CameraScreen( - padding = padding, - ) -} - -@Composable -internal fun CameraScreen( - padding: PaddingValues, -) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text(text = "CameraScreen") - } -} diff --git a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadCheckScreen.kt b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadCheckScreen.kt new file mode 100644 index 00000000..fe4fa85c --- /dev/null +++ b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadCheckScreen.kt @@ -0,0 +1,195 @@ +package com.nexters.ilab.android.feature.camera + +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.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.nexters.ilab.android.core.designsystem.R +import com.nexters.ilab.android.core.designsystem.theme.Contents1 +import com.nexters.ilab.android.core.designsystem.theme.Contents2 +import com.nexters.ilab.android.core.designsystem.theme.Gray500 +import com.nexters.ilab.android.core.designsystem.theme.PurpleBlue200 +import com.nexters.ilab.android.core.designsystem.theme.PurpleBlue900 +import com.nexters.ilab.android.core.designsystem.theme.Subtitle1 +import com.nexters.ilab.android.core.designsystem.theme.Title1 +import com.nexters.ilab.core.ui.DevicePreview +import com.nexters.ilab.core.ui.component.ILabButton +import com.nexters.ilab.core.ui.component.ILabTopAppBar +import com.nexters.ilab.core.ui.component.NetworkImage +import com.nexters.ilab.core.ui.component.TopAppBarNavigationType + +@Suppress("unused") +@Composable +internal fun UploadCheckRoute( + onBackClick: () -> Unit, + viewModel: CameraViewModel = hiltViewModel(), +) { + UploadCheckScreen(onBackClick = onBackClick) +} + +@Composable +private fun UploadCheckScreen( + onBackClick: () -> Unit, +) { + Column { + UploadCheckTopAppBar(onBackClick = onBackClick) + UploadCheckContent() + } +} + +@Composable +private fun UploadCheckTopAppBar( + onBackClick: () -> Unit, +) { + ILabTopAppBar( + titleRes = R.string.upload_check_top_title, + navigationType = TopAppBarNavigationType.Back, + navigationIconContentDescription = "navigation Icon", + modifier = Modifier + .statusBarsPadding() + .height(56.dp), + onNavigationClick = onBackClick, + ) +} + +@Composable +private fun UploadCheckContent() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = stringResource(id = R.string.check_guide_twice), + style = Title1, + color = Color.Black, + ) + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = stringResource(id = R.string.check_needed_for_accurate_result), + style = Contents1, + color = Gray500, + ) + Spacer(modifier = Modifier.height(36.dp)) + NetworkImage( + imageUrl = "https://picsum.photos/300/300", + contentDescription = "upload image", + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 92.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(12.dp)), + ) + Spacer(modifier = Modifier.height(36.dp)) + GuideRow( + resId = R.drawable.ic_guide_right, + contentDescription = "guide right 1", + text = stringResource(id = R.string.choice_good_example_first), + ) + Spacer(modifier = Modifier.height(16.dp)) + GuideRow( + resId = R.drawable.ic_guide_right, + contentDescription = "guide right 2", + text = stringResource(id = R.string.choice_good_example_second), + ) + Spacer(modifier = Modifier.height(16.dp)) + GuideRow( + resId = R.drawable.ic_guide_error, + contentDescription = "guide error 1", + text = stringResource(id = R.string.avoid_bad_example_first), + ) + Spacer(modifier = Modifier.height(16.dp)) + GuideRow( + resId = R.drawable.ic_guide_error, + contentDescription = "guide error 2", + text = stringResource(id = R.string.avoid_bad_example_second), + ) + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(start = 4.dp, end = 4.dp, bottom = 18.dp), + ) { + ILabButton( + onClick = {}, + modifier = Modifier + .weight(1f) + .height(60.dp) + .padding(end = 4.dp), + containerColor = PurpleBlue200, + contentColor = PurpleBlue900, + text = { + Text( + text = stringResource(id = R.string.change_photo), + style = Subtitle1, + ) + }, + ) + ILabButton( + onClick = {}, + modifier = Modifier + .weight(1f) + .height(60.dp) + .padding(start = 4.dp), + text = { + Text( + text = stringResource(id = R.string.check), + style = Subtitle1, + ) + }, + ) + } + } +} + +@Composable +fun GuideRow( + resId: Int, + contentDescription: String, + text: String, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = resId), + contentDescription = contentDescription, + // https://stackoverflow.com/questions/66211616/how-to-change-android-jetpack-compose-bottomappbar-icon-tint-color + tint = Color.Unspecified, + ) + Spacer(Modifier.width(12.dp)) + Text( + text = text, + style = Contents2, + color = Color.Black, + ) + } +} + +@DevicePreview +@Composable +fun UploadCheckScreenPreview() { + UploadCheckScreen(onBackClick = {}) +} diff --git a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadScreen.kt b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadScreen.kt new file mode 100644 index 00000000..dbd1658e --- /dev/null +++ b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/UploadScreen.kt @@ -0,0 +1,209 @@ +package com.nexters.ilab.android.feature.camera + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +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.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.nexters.ilab.android.core.designsystem.R +import com.nexters.ilab.android.core.designsystem.theme.Contents2 +import com.nexters.ilab.android.core.designsystem.theme.PurpleBlue200 +import com.nexters.ilab.android.core.designsystem.theme.PurpleBlue900 +import com.nexters.ilab.android.core.designsystem.theme.Subtitle1 +import com.nexters.ilab.android.core.designsystem.theme.SystemGreen +import com.nexters.ilab.android.core.designsystem.theme.SystemRed +import com.nexters.ilab.android.core.designsystem.theme.Title2 +import com.nexters.ilab.core.ui.DevicePreview +import com.nexters.ilab.core.ui.component.ExampleImage +import com.nexters.ilab.core.ui.component.ILabButton +import com.nexters.ilab.core.ui.component.ILabTopAppBar +import com.nexters.ilab.core.ui.component.TopAppBarNavigationType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Suppress("unused") +@Composable +internal fun UploadRoute( + onBackClick: () -> Unit, + onNavigateToUploadCheck: () -> Unit, + viewModel: CameraViewModel = hiltViewModel(), +) { + UploadScreen( + onBackClick = onBackClick, + onNavigateToUploadCheck = onNavigateToUploadCheck, + ) +} + +@Composable +internal fun UploadScreen( + onBackClick: () -> Unit, + onNavigateToUploadCheck: () -> Unit, +) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + UploadTopAppBar(onBackClick = onBackClick) + UploadContent(onNavigateToUploadCheck = onNavigateToUploadCheck) + } +} + +@Composable +private fun UploadTopAppBar( + onBackClick: () -> Unit, +) { + ILabTopAppBar( + titleRes = R.string.upload_top_title, + navigationType = TopAppBarNavigationType.Back, + navigationIconContentDescription = "navigation Icon", + modifier = Modifier + .statusBarsPadding() + .height(56.dp), + onNavigationClick = onBackClick, + ) +} + +@Composable +private fun UploadContent( + onNavigateToUploadCheck: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = SystemGreen)) { + append(stringResource(id = R.string.good)) + } + withStyle(style = SpanStyle(color = Color.Black)) { + append(stringResource(id = R.string.good_example)) + } + }, + style = Title2, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.good_example_description), + style = Contents2, + color = Color.Black, + ) + Spacer(modifier = Modifier.height(20.dp)) + ImageRow(images = goodExamples) + Spacer(modifier = Modifier.height(42.dp)) + Text( + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = SystemRed)) { + append(stringResource(id = R.string.bad)) + } + withStyle(style = SpanStyle(color = Color.Black)) { + append(stringResource(id = R.string.bad_example)) + } + }, + style = Title2, + color = Color.Black, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.bad_example_description), + style = Contents2, + color = Color.Black, + ) + Spacer(modifier = Modifier.height(20.dp)) + ImageRow(images = badExamples) + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(start = 4.dp, end = 4.dp, bottom = 18.dp), + ) { + ILabButton( + onClick = {}, + modifier = Modifier + .weight(1f) + .height(60.dp) + .padding(end = 4.dp), + containerColor = PurpleBlue200, + contentColor = PurpleBlue900, + text = { + Text( + text = stringResource(id = R.string.photo_library), + style = Subtitle1, + ) + }, + ) + ILabButton( + onClick = onNavigateToUploadCheck, + modifier = Modifier + .weight(1f) + .height(60.dp) + .padding(start = 4.dp), + text = { + Text( + text = stringResource(id = R.string.take_photo), + style = Subtitle1, + ) + }, + ) + } + } +} + +val goodExamples = persistentListOf( + Pair(R.drawable.ic_good_example1, "good example 1"), + Pair(R.drawable.ic_good_example2, "good example 2"), + Pair(R.drawable.ic_good_example3, "good example 3"), +) +val badExamples = persistentListOf( + Pair(R.drawable.ic_bad_example1, "bad example 1"), + Pair(R.drawable.ic_bad_example2, "bad example 2"), + Pair(R.drawable.ic_bad_example3, "bad example 3"), +) + +@Composable +fun ImageRow(images: ImmutableList>) { + Row(modifier = Modifier.fillMaxWidth()) { + images.forEachIndexed { index, (resId, contentDescription) -> + if (index > 0) Spacer(Modifier.width(12.dp)) + ExampleImage( + resId = resId, + contentDescription = contentDescription, + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .weight(1f), + ) + } + } +} + +@DevicePreview +@Composable +fun UploadScreenPreview() { + UploadScreen( + onBackClick = {}, + onNavigateToUploadCheck = {}, + ) +} diff --git a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/navigation/CameraNavigation.kt b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/navigation/CameraNavigation.kt index 4553ad6f..76dd5e5b 100644 --- a/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/navigation/CameraNavigation.kt +++ b/feature/camera/src/main/kotlin/com/nexters/ilab/android/feature/camera/navigation/CameraNavigation.kt @@ -1,26 +1,44 @@ package com.nexters.ilab.android.feature.camera.navigation -import androidx.compose.foundation.layout.PaddingValues import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.nexters.ilab.android.feature.camera.CameraRoute +import androidx.navigation.compose.navigation +import com.nexters.ilab.android.feature.camera.UploadCheckRoute +import com.nexters.ilab.android.feature.camera.UploadRoute const val CAMERA_ROUTE = "camera_route" +const val UPLOAD_ROUTE = "upload_route" +const val UPLOAD_CHECK_ROUTE = "upload_check_route" fun NavController.navigateToCamera(navOptions: NavOptions) { navigate(CAMERA_ROUTE, navOptions) } +fun NavController.navigateToUploadCheck() { + navigate(UPLOAD_CHECK_ROUTE) +} + fun NavGraphBuilder.cameraNavGraph( - padding: PaddingValues, - onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onBackClick: () -> Unit, + onNavigateToUploadCheck: () -> Unit, ) { - composable(route = CAMERA_ROUTE) { - CameraRoute( - padding = padding, - onShowErrorSnackBar = onShowErrorSnackBar, - ) + navigation( + startDestination = UPLOAD_ROUTE, + route = CAMERA_ROUTE, + ) { + composable(route = UPLOAD_ROUTE) { + UploadRoute( + onBackClick = onBackClick, + onNavigateToUploadCheck = onNavigateToUploadCheck, + ) + } + + composable(route = UPLOAD_CHECK_ROUTE) { + UploadCheckRoute( + onBackClick = onBackClick, + ) + } } } diff --git a/feature/main/src/main/AndroidManifest.xml b/feature/main/src/main/AndroidManifest.xml index 1c51eb10..1705e95c 100644 --- a/feature/main/src/main/AndroidManifest.xml +++ b/feature/main/src/main/AndroidManifest.xml @@ -8,12 +8,6 @@ android:exported="false" android:theme="@style/Theme.Ilab"> - - - - - - diff --git a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainActivity.kt b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainActivity.kt index 8cadaf1e..42a2a6ec 100644 --- a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainActivity.kt +++ b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.core.view.WindowCompat import com.nexters.ilab.android.core.designsystem.theme.ILabTheme import com.nexters.ilab.android.feature.navigator.LoginNavigator import dagger.hilt.android.AndroidEntryPoint @@ -23,6 +24,8 @@ class MainActivity : ComponentActivity() { val isDarkTheme = false val navigator: MainNavController = rememberMainNavController() + WindowCompat.setDecorFitsSystemWindows(window, false) + ILabTheme(darkTheme = isDarkTheme) { MainScreen( onChangeDarkTheme = { isDarkTheme -> viewModel.toggleDarkTheme(isDarkTheme) }, diff --git a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainNavController.kt b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainNavController.kt index eb958ec2..0ceb7ccd 100644 --- a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainNavController.kt +++ b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainNavController.kt @@ -9,6 +9,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import com.nexters.ilab.android.feature.camera.navigation.navigateToCamera +import com.nexters.ilab.android.feature.camera.navigation.navigateToUploadCheck import com.nexters.ilab.android.feature.home.navigation.HOME_ROUTE import com.nexters.ilab.android.feature.home.navigation.navigateToHome import com.nexters.ilab.android.feature.mypage.navigation.navigateToMyPage @@ -44,6 +45,10 @@ internal class MainNavController( } } + fun navigateToUploadCheck() { + navController.navigateToUploadCheck() + } + fun navigateToSetting() { navController.navigateToSetting() } diff --git a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainScreen.kt b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainScreen.kt index 286f698b..f7f18d8b 100644 --- a/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainScreen.kt +++ b/feature/main/src/main/kotlin/com/nexters/ilab/android/feature/main/MainScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource @@ -82,8 +83,8 @@ internal fun MainScreen( ) cameraNavGraph( - padding = padding, - onShowErrorSnackBar = onShowErrorSnackBar, + onBackClick = navigator::popBackStackIfNotHome, + onNavigateToUploadCheck = navigator::navigateToUploadCheck, ) myPageNavGraph( @@ -110,6 +111,7 @@ internal fun MainScreen( ) }, snackbarHost = { SnackbarHost(snackBarHostState) }, + containerColor = Color.White, ) }