diff --git a/Week03/Deku2/.gitignore b/Week03/Deku2/.gitignore new file mode 100644 index 0000000..841dd0e --- /dev/null +++ b/Week03/Deku2/.gitignore @@ -0,0 +1,22 @@ +*.iml +.gradle +.kotlin/ +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +*.apk +*.ap_ +*.aab +*.jks +*.keystore +*.hprof diff --git a/Week03/Deku2/app/.gitignore b/Week03/Deku2/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Week03/Deku2/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Week03/Deku2/app/build.gradle.kts b/Week03/Deku2/app/build.gradle.kts new file mode 100644 index 0000000..84404e5 --- /dev/null +++ b/Week03/Deku2/app/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "com.example.deku" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "com.example.deku" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + buildFeatures { + compose = true + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.navigation.compose) + implementation(libs.kotlinx.serialization.json) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) +} diff --git a/Week03/Deku2/app/proguard-rules.pro b/Week03/Deku2/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Week03/Deku2/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Week03/Deku2/app/src/androidTest/java/com/example/deku/ExampleInstrumentedTest.kt b/Week03/Deku2/app/src/androidTest/java/com/example/deku/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..05aaa19 --- /dev/null +++ b/Week03/Deku2/app/src/androidTest/java/com/example/deku/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.deku + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.deku", appContext.packageName) + } +} \ No newline at end of file diff --git a/Week03/Deku2/app/src/main/AndroidManifest.xml b/Week03/Deku2/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..340e1cf --- /dev/null +++ b/Week03/Deku2/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/MainActivity.kt b/Week03/Deku2/app/src/main/java/com/example/deku/MainActivity.kt new file mode 100644 index 0000000..397e847 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/MainActivity.kt @@ -0,0 +1,25 @@ +package com.example.deku + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import com.example.deku.core.common.DEFAULT_HOME_TITLE +import com.example.deku.core.common.EXTRA_HOME_TITLE +import com.example.deku.core.designsystem.theme.DekuTheme +import com.example.deku.feature.main.MainScreen + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + val homeTitle = intent.getStringExtra(EXTRA_HOME_TITLE) ?: DEFAULT_HOME_TITLE + + setContent { + DekuTheme { + MainScreen(homeTitle = homeTitle) + } + } + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/SplashActivity.kt b/Week03/Deku2/app/src/main/java/com/example/deku/SplashActivity.kt new file mode 100644 index 0000000..b6603f9 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/SplashActivity.kt @@ -0,0 +1,32 @@ +package com.example.deku + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import com.example.deku.core.common.EXTRA_HOME_TITLE +import com.example.deku.core.common.SPLASH_HOME_TITLE +import com.example.deku.core.designsystem.theme.DekuTheme +import com.example.deku.feature.splash.SplashScreen + +class SplashActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + + setContent { + DekuTheme { + SplashScreen( + onTimeout = { + val intent = Intent(this, MainActivity::class.java).apply { + putExtra(EXTRA_HOME_TITLE, SPLASH_HOME_TITLE) + } + startActivity(intent) + finish() + } + ) + } + } + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/common/AppConstants.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/common/AppConstants.kt new file mode 100644 index 0000000..411a738 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/common/AppConstants.kt @@ -0,0 +1,6 @@ +package com.example.deku.core.common + +const val EXTRA_HOME_TITLE = "com.example.deku.extra.HOME_TITLE" +const val SPLASH_HOME_TITLE = "NIKE" + +internal const val DEFAULT_HOME_TITLE = "Discover" diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/component/MainBottomBar.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/component/MainBottomBar.kt new file mode 100644 index 0000000..cd1c862 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/component/MainBottomBar.kt @@ -0,0 +1,109 @@ +package com.example.deku.core.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.fillMaxHeight +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.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +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.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.deku.R +import com.example.deku.core.designsystem.ColorDivider +import com.example.deku.core.designsystem.ColorNavUnselected +import com.example.deku.core.designsystem.ColorTextPrimary +import com.example.deku.navigation.MainRouteName + +private data class BottomTabItem( + val label: String, + @param:DrawableRes val iconRes: Int, + val routeName: String +) + +private val bottomTabs = listOf( + BottomTabItem("홈", R.drawable.home, MainRouteName.HOME), + BottomTabItem("구매하기", R.drawable.shop, MainRouteName.SHOP), + BottomTabItem("위시리스트", R.drawable.heart, MainRouteName.WISH_LIST), + BottomTabItem("장바구니", R.drawable.cart, MainRouteName.CART), + BottomTabItem("프로필", R.drawable.profile, MainRouteName.PROFILE) +) + +@Composable +fun MainBottomBar( + currentRoute: String?, + onTabSelected: (String) -> Unit +) { + Surface( + color = Color.White, + shadowElevation = 0.dp + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.White) + .navigationBarsPadding() + ) { + HorizontalDivider(color = ColorDivider, thickness = 1.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + .padding(horizontal = 14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + bottomTabs.forEach { item -> + val selected = currentRoute == item.routeName + val color = if (selected) ColorTextPrimary else ColorNavUnselected + + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(8.dp)) + .clickable { onTabSelected(item.routeName) } + .padding(vertical = 6.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(id = item.iconRes), + contentDescription = item.label, + tint = color, + modifier = Modifier.size(29.dp) + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = item.label, + color = color, + fontSize = 10.sp, + lineHeight = 12.sp, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + textAlign = TextAlign.Center + ) + } + } + } + } + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/AppColors.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/AppColors.kt new file mode 100644 index 0000000..0be9a1b --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/AppColors.kt @@ -0,0 +1,9 @@ +package com.example.deku.core.designsystem + +import androidx.compose.ui.graphics.Color + +val ColorTextPrimary = Color(0xFF111111) +val ColorTextSecondary = Color(0xFF5F5F5F) +val ColorNavUnselected = Color(0xFF8A8A8A) +val ColorFrameBackground = Color(0xFFF7F7F7) +val ColorDivider = Color(0x1F203126) diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Color.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Color.kt new file mode 100644 index 0000000..95167c1 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Color.kt @@ -0,0 +1,9 @@ +package com.example.deku.core.designsystem.theme + +import androidx.compose.ui.graphics.Color + +val NikeBlack = Color(0xFF111111) +val NikeWhite = Color(0xFFFFFFFF) +val NikeGray = Color(0xFF8A8A8A) +val NikeDarkGray = Color(0xFF2C2C2C) +val NikeFrameBackground = Color(0xFFF7F7F7) diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Theme.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Theme.kt new file mode 100644 index 0000000..b5e8e25 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Theme.kt @@ -0,0 +1,61 @@ +package com.example.deku.core.designsystem.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = NikeWhite, + secondary = NikeGray, + tertiary = NikeFrameBackground, + background = NikeBlack, + surface = NikeBlack, + onPrimary = NikeBlack, + onSecondary = NikeWhite, + onTertiary = NikeBlack, + onBackground = NikeWhite, + onSurface = NikeWhite +) + +private val LightColorScheme = lightColorScheme( + primary = NikeBlack, + secondary = NikeDarkGray, + tertiary = NikeFrameBackground, + background = NikeWhite, + surface = NikeWhite, + onPrimary = NikeWhite, + onSecondary = NikeWhite, + onTertiary = NikeBlack, + onBackground = NikeBlack, + onSurface = NikeBlack +) + +@Composable +fun DekuTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = false, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Type.kt b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Type.kt new file mode 100644 index 0000000..2068cd0 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/core/designsystem/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.deku.core.designsystem.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/cart/CartScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/cart/CartScreen.kt new file mode 100644 index 0000000..51b7e0f --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/cart/CartScreen.kt @@ -0,0 +1,101 @@ +package com.example.deku.feature.cart + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.text.font.FontWeight +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.sp +import com.example.deku.R +import com.example.deku.core.designsystem.ColorFrameBackground +import com.example.deku.core.designsystem.ColorNavUnselected +import com.example.deku.core.designsystem.ColorTextPrimary +import com.example.deku.core.designsystem.theme.DekuTheme + +@Composable +fun CartScreen( + onOrderClick: () -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp) + .padding(top = 40.dp) + ) { + Column( + modifier = Modifier.align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Surface( + modifier = Modifier.size(70.dp), + shape = CircleShape, + color = ColorFrameBackground + ) { + Icon( + painter = painterResource(id = R.drawable.cart), + contentDescription = null, + tint = ColorNavUnselected, + modifier = Modifier.padding(17.dp) + ) + } + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = "장바구니가 비어있습니다\n제품을 추가하면 여기에 표시됩니다.", + color = ColorTextPrimary, + textAlign = TextAlign.Center, + fontSize = 16.sp, + lineHeight = 24.sp + ) + } + + Button( + onClick = onOrderClick, + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp, bottom = 8.dp) + .height(52.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Black, + contentColor = Color.White + ), + shape = RoundedCornerShape(26.dp) + ) { + Text( + text = "주문하기", + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun CartScreenPreview() { + DekuTheme { + CartScreen(onOrderClick = {}) + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/home/HomeScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/home/HomeScreen.kt new file mode 100644 index 0000000..c64e78b --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/home/HomeScreen.kt @@ -0,0 +1,104 @@ +package com.example.deku.feature.home + +import android.app.Activity +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.deku.R +import com.example.deku.core.common.SPLASH_HOME_TITLE +import com.example.deku.core.designsystem.ColorTextPrimary +import com.example.deku.core.designsystem.ColorTextSecondary +import com.example.deku.core.designsystem.theme.DekuTheme +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +private const val BACK_PRESS_INTERVAL_MILLIS = 2_000L + +@Composable +fun HomeScreen( + title: String, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val activity = context as? Activity + var lastBackPressedAt by remember { mutableStateOf(0L) } + val today = remember { currentKoreanDate() } + + BackHandler { + val now = System.currentTimeMillis() + if (now - lastBackPressedAt <= BACK_PRESS_INTERVAL_MILLIS) { + activity?.finish() + } else { + lastBackPressedAt = now + Toast.makeText(context, "한 번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT).show() + } + } + + Column( + modifier = modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp) + .padding(top = 50.dp) + ) { + Text( + text = title, + color = ColorTextPrimary, + fontSize = 30.sp, + lineHeight = 36.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = today, + color = ColorTextSecondary, + fontSize = 20.sp, + lineHeight = 26.sp + ) + Spacer(modifier = Modifier.height(80.dp)) + Image( + painter = painterResource(id = R.drawable.nike_logo), + contentDescription = stringResource(id = R.string.home_brand_logo), + modifier = Modifier + .align(Alignment.CenterHorizontally) + .size(220.dp) + ) + } +} + +private fun currentKoreanDate(): String { + val formatter = SimpleDateFormat("M월 d일 EEEE", Locale.KOREAN) + return formatter.format(Date()) +} + +@Preview(showBackground = true) +@Composable +private fun HomeScreenPreview() { + DekuTheme { + HomeScreen(title = SPLASH_HOME_TITLE) + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/main/MainScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/main/MainScreen.kt new file mode 100644 index 0000000..9c98084 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/main/MainScreen.kt @@ -0,0 +1,43 @@ +package com.example.deku.feature.main + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.example.deku.core.component.MainBottomBar +import com.example.deku.navigation.MainNavGraph +import com.example.deku.navigation.currentBaseRoute +import com.example.deku.navigation.navigateToBottomTab + +@Composable +fun MainScreen(homeTitle: String) { + val navController = rememberNavController() + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route.currentBaseRoute() + + Scaffold( + modifier = Modifier.fillMaxSize(), + containerColor = Color.White, + bottomBar = { + MainBottomBar( + currentRoute = currentRoute, + onTabSelected = { routeName -> + navController.navigateToBottomTab(routeName, homeTitle) + } + ) + } + ) { innerPadding -> + MainNavGraph( + navController = navController, + homeTitle = homeTitle, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/profile/ProfileScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/profile/ProfileScreen.kt new file mode 100644 index 0000000..897589c --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/profile/ProfileScreen.kt @@ -0,0 +1,28 @@ +package com.example.deku.feature.profile + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp + +@Composable +fun ProfileScreen(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.White), + contentAlignment = Alignment.TopStart + ) { + Text( + text = "Hello blank fragment", + color = MaterialTheme.colorScheme.onBackground, + fontSize = 16.sp + ) + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/shop/ShopScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/shop/ShopScreen.kt new file mode 100644 index 0000000..e5ae016 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/shop/ShopScreen.kt @@ -0,0 +1,76 @@ +package com.example.deku.feature.shop + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.deku.core.designsystem.ColorNavUnselected +import com.example.deku.core.designsystem.ColorTextPrimary + +@Composable +fun ShopScreen(modifier: Modifier = Modifier) { + val tabTitles = listOf("전체", "Tops & T-Shirts", "Shoes") + var selectedTabIndex by remember { mutableStateOf(0) } + + Column( + modifier = modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp) + .padding(top = 40.dp) + ) { + TabRow( + selectedTabIndex = selectedTabIndex, + containerColor = Color.White, + contentColor = ColorTextPrimary + ) { + tabTitles.forEachIndexed { index, title -> + Tab( + selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + selectedContentColor = ColorTextPrimary, + unselectedContentColor = ColorNavUnselected, + text = { + Text( + text = title, + fontSize = if (title.length > 8) 13.sp else 14.sp, + lineHeight = 18.sp, + fontWeight = if (selectedTabIndex == index) { + FontWeight.Bold + } else { + FontWeight.Normal + }, + maxLines = 1 + ) + } + ) + } + } + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopStart + ) { + Text( + text = "Hello blank fragment", + color = MaterialTheme.colorScheme.onBackground, + fontSize = 16.sp + ) + } + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/splash/SplashScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/splash/SplashScreen.kt new file mode 100644 index 0000000..6f1461c --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/splash/SplashScreen.kt @@ -0,0 +1,64 @@ +package com.example.deku.feature.splash + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.deku.R +import com.example.deku.core.common.SPLASH_HOME_TITLE +import com.example.deku.core.designsystem.theme.DekuTheme +import kotlinx.coroutines.delay + +@Composable +fun SplashScreen(onTimeout: () -> Unit) { + LaunchedEffect(Unit) { + delay(2_000) + onTimeout() + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = SPLASH_HOME_TITLE, + color = Color(0xFF111111), + fontSize = 50.sp, + lineHeight = 58.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(16.dp)) + Image( + painter = painterResource(id = R.drawable.splash_logo), + contentDescription = stringResource(id = R.string.splash_brand_logo), + modifier = Modifier.size(width = 100.dp, height = 80.dp) + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun SplashScreenPreview() { + DekuTheme { + SplashScreen(onTimeout = {}) + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/feature/wishlist/WishListScreen.kt b/Week03/Deku2/app/src/main/java/com/example/deku/feature/wishlist/WishListScreen.kt new file mode 100644 index 0000000..a811902 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/feature/wishlist/WishListScreen.kt @@ -0,0 +1,52 @@ +package com.example.deku.feature.wishlist + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +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.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.deku.core.designsystem.ColorTextPrimary +import com.example.deku.core.designsystem.ColorTextSecondary + +@Composable +fun WishListScreen(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .background(Color.White) + .padding(horizontal = 24.dp) + .padding(top = 70.dp) + ) { + Text( + text = "위시리스트", + color = ColorTextPrimary, + fontSize = 30.sp, + lineHeight = 36.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(120.dp)) + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + Text( + text = "관심 상품이 없습니다\n마음에 드는 상품을 저장해보세요.", + color = ColorTextSecondary, + textAlign = TextAlign.Center, + fontSize = 16.sp, + lineHeight = 24.sp + ) + } + } +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainNavGraph.kt b/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainNavGraph.kt new file mode 100644 index 0000000..5af426c --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainNavGraph.kt @@ -0,0 +1,75 @@ +package com.example.deku.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.example.deku.feature.cart.CartScreen +import com.example.deku.feature.home.HomeScreen +import com.example.deku.feature.profile.ProfileScreen +import com.example.deku.feature.shop.ShopScreen +import com.example.deku.feature.wishlist.WishListScreen + +@Composable +fun MainNavGraph( + navController: NavHostController, + homeTitle: String, + modifier: Modifier = Modifier +) { + NavHost( + navController = navController, + startDestination = MainRoute.Home(title = homeTitle), + modifier = modifier + ) { + composable { backStackEntry -> + val route = backStackEntry.toRoute() + HomeScreen(title = route.title) + } + composable { + ShopScreen() + } + composable { + WishListScreen() + } + composable { + CartScreen( + onOrderClick = { + navController.navigateToBottomTab(MainRouteName.SHOP, homeTitle) + } + ) + } + composable { + ProfileScreen() + } + } +} + +fun NavController.navigateToBottomTab(routeName: String, homeTitle: String) { + when (routeName) { + MainRouteName.HOME -> navigateBottom(MainRoute.Home(title = homeTitle)) + MainRouteName.SHOP -> navigateBottom(MainRoute.Shop) + MainRouteName.WISH_LIST -> navigateBottom(MainRoute.WishList) + MainRouteName.CART -> navigateBottom(MainRoute.Cart) + MainRouteName.PROFILE -> navigateBottom(MainRoute.Profile) + } +} + +private fun NavController.navigateBottom(route: T) { + navigate(route) { + popUpTo(graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } +} + +fun String?.currentBaseRoute(): String? { + return this + ?.substringBefore("/") + ?.substringBefore("?") +} diff --git a/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainRoute.kt b/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainRoute.kt new file mode 100644 index 0000000..a477765 --- /dev/null +++ b/Week03/Deku2/app/src/main/java/com/example/deku/navigation/MainRoute.kt @@ -0,0 +1,34 @@ +package com.example.deku.navigation + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +object MainRouteName { + const val HOME = "home" + const val SHOP = "shop" + const val WISH_LIST = "wish_list" + const val CART = "cart" + const val PROFILE = "profile" +} + +sealed interface MainRoute { + @Serializable + @SerialName(MainRouteName.HOME) + data class Home(val title: String) : MainRoute + + @Serializable + @SerialName(MainRouteName.SHOP) + data object Shop : MainRoute + + @Serializable + @SerialName(MainRouteName.WISH_LIST) + data object WishList : MainRoute + + @Serializable + @SerialName(MainRouteName.CART) + data object Cart : MainRoute + + @Serializable + @SerialName(MainRouteName.PROFILE) + data object Profile : MainRoute +} diff --git a/Week03/Deku2/app/src/main/res/drawable/cart.xml b/Week03/Deku2/app/src/main/res/drawable/cart.xml new file mode 100644 index 0000000..3b6e816 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/cart.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/heart.xml b/Week03/Deku2/app/src/main/res/drawable/heart.xml new file mode 100644 index 0000000..6597a4a --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/heart.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/home.xml b/Week03/Deku2/app/src/main/res/drawable/home.xml new file mode 100644 index 0000000..20cb4d6 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/home.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/ic_launcher_background.xml b/Week03/Deku2/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/ic_launcher_foreground.xml b/Week03/Deku2/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Week03/Deku2/app/src/main/res/drawable/nike_logo.png b/Week03/Deku2/app/src/main/res/drawable/nike_logo.png new file mode 100644 index 0000000..d73dd96 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/drawable/nike_logo.png differ diff --git a/Week03/Deku2/app/src/main/res/drawable/profile.xml b/Week03/Deku2/app/src/main/res/drawable/profile.xml new file mode 100644 index 0000000..1fc97b8 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/profile.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/shop.xml b/Week03/Deku2/app/src/main/res/drawable/shop.xml new file mode 100644 index 0000000..483d117 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/drawable/shop.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/Week03/Deku2/app/src/main/res/drawable/splash_logo.png b/Week03/Deku2/app/src/main/res/drawable/splash_logo.png new file mode 100644 index 0000000..d6215d1 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/drawable/splash_logo.png differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/Week03/Deku2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/Week03/Deku2/app/src/main/res/values/colors.xml b/Week03/Deku2/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/Week03/Deku2/app/src/main/res/values/strings.xml b/Week03/Deku2/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..193be95 --- /dev/null +++ b/Week03/Deku2/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Deku + NIKE logo + NIKE splash logo + diff --git a/Week03/Deku2/app/src/main/res/values/themes.xml b/Week03/Deku2/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..2356cbe --- /dev/null +++ b/Week03/Deku2/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +