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,
)
}