diff --git a/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt b/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt index d167d4b..91cd311 100644 --- a/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt +++ b/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt @@ -13,13 +13,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument import com.sampoom.android.R import com.sampoom.android.feature.auth.ui.LoginScreen import com.sampoom.android.feature.auth.ui.SignUpScreen +import com.sampoom.android.feature.part.ui.PartListScreen import com.sampoom.android.feature.part.ui.PartScreen const val ROUTE_LOGIN = "login" @@ -34,6 +37,8 @@ const val ROUTE_ORDERS = "orders" // Detail Screen const val ROUTE_PARTS = "parts" +const val ROUTE_PART_LIST = "parts/{agencyId}/group/{groupId}" +fun routePartList(agencyId: Long, groupId: Long): String = "parts/$agencyId/group/$groupId" const val ROUTE_EMPLOYEE = "employee" const val ROUTE_SETTINGS = "settings" @@ -53,18 +58,19 @@ fun AppNavHost() { val navController = rememberNavController() // TODO: 임시 로그인 상태 확인 -> AuthRepository에서 확인하도록 변경 - val isLoggedIn = false + val isLoggedIn = true NavHost( navController = navController, startDestination = if (isLoggedIn) ROUTE_HOME else ROUTE_LOGIN ) { composable(ROUTE_LOGIN) { - LoginScreen(onSuccess = { - navController.navigate(ROUTE_HOME) { - popUpTo(ROUTE_LOGIN) { inclusive = true } // 로그인 화면 스택 제거 - } - }, + LoginScreen( + onSuccess = { + navController.navigate(ROUTE_HOME) { + popUpTo(ROUTE_LOGIN) { inclusive = true } // 로그인 화면 스택 제거 + } + }, onNavigateSignUp = { navController.navigate(ROUTE_SIGNUP) }) @@ -82,7 +88,29 @@ fun AppNavHost() { ) } composable(ROUTE_HOME) { MainScreen(navController) } - composable(ROUTE_PARTS) { PartScreen() } + composable(ROUTE_PARTS) { + PartScreen( + onNavigateBack = { + navController.navigateUp() + }, + onNavigatePartList = { group -> + navController.navigate(routePartList(1, group.id)) + } + ) + } + composable( + ROUTE_PART_LIST, + arguments = listOf( + navArgument("agencyId") { type = NavType.LongType }, + navArgument("groupId") { type = NavType.LongType } + ) + ) { + PartListScreen( + onNavigateBack = { + navController.navigateUp() + } + ) + } } } @@ -122,7 +150,10 @@ fun PartsFab(navController: NavHostController) { } } ) { - Icon(painterResource(R.drawable.parts), contentDescription = stringResource(R.string.part_title)) + Icon( + painterResource(R.drawable.parts), + contentDescription = stringResource(R.string.part_title) + ) } } @@ -141,7 +172,12 @@ fun BottomNavigationBar(navController: NavHostController) { val currentDestination = navBackStackEntry?.destination NavigationBarItem( - icon = { Icon(painterResource(id = item.icon), contentDescription = stringResource(item.title)) }, + icon = { + Icon( + painterResource(id = item.icon), + contentDescription = stringResource(item.title) + ) + }, label = { Text(stringResource(item.title)) }, selected = currentDestination?.route == item.route, onClick = { diff --git a/app/src/main/java/com/sampoom/android/core/ui/component/EmptyContent.kt b/app/src/main/java/com/sampoom/android/core/ui/component/EmptyContent.kt new file mode 100644 index 0000000..83439f1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/core/ui/component/EmptyContent.kt @@ -0,0 +1,20 @@ +package com.sampoom.android.core.ui.component + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun EmptyContent( + message: String, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + Text(message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/core/ui/component/ErrorContent.kt b/app/src/main/java/com/sampoom/android/core/ui/component/ErrorContent.kt new file mode 100644 index 0000000..6e102cb --- /dev/null +++ b/app/src/main/java/com/sampoom/android/core/ui/component/ErrorContent.kt @@ -0,0 +1,37 @@ +package com.sampoom.android.core.ui.component + +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.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sampoom.android.R +import com.sampoom.android.core.ui.theme.FailRed + +@Composable +fun ErrorContent( + onRetry: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.common_error), + color = FailRed + ) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = onRetry) { + Text(stringResource(R.string.common_retry)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt b/app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt index 72cdc9c..fe07c9a 100644 --- a/app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt +++ b/app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt @@ -4,6 +4,222 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +val primaryLight = Color(0xFF4C4AC8) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFF6565E2) +val onPrimaryContainerLight = Color(0xFFFFFBFF) +val secondaryLight = Color(0xFF5B5D72) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFFE6E6FF) +val onSecondaryContainerLight = Color(0xFF65667B) +val tertiaryLight = Color(0xFF5D5F5F) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFF5F5F5) +val onTertiaryContainerLight = Color(0xFF6F7070) +val errorLight = Color(0xFFAD3035) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFF6C6C) +val onErrorContainerLight = Color(0xFF6E0011) +val backgroundLight = Color(0xFFFCF8FF) +val onBackgroundLight = Color(0xFF1B1B23) +val surfaceLight = Color(0xFFFCF8F8) +val onSurfaceLight = Color(0xFF1C1B1B) +val surfaceVariantLight = Color(0xFFE0E3E3) +val onSurfaceVariantLight = Color(0xFF444748) +val outlineLight = Color(0xFF747878) +val outlineVariantLight = Color(0xFFC4C7C8) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF313030) +val inverseOnSurfaceLight = Color(0xFFF4F0EF) +val inversePrimaryLight = Color(0xFFC2C1FF) +val surfaceDimLight = Color(0xFFDDD9D9) +val surfaceBrightLight = Color(0xFFFCF8F8) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF6F3F2) +val surfaceContainerLight = Color(0xFFF1EDEC) +val surfaceContainerHighLight = Color(0xFFEBE7E7) +val surfaceContainerHighestLight = Color(0xFFE5E2E1) + +val primaryLightMediumContrast = Color(0xFF231AA2) +val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) +val primaryContainerLightMediumContrast = Color(0xFF5E5DDA) +val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val secondaryLightMediumContrast = Color(0xFF333548) +val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) +val secondaryContainerLightMediumContrast = Color(0xFF6A6B81) +val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryLightMediumContrast = Color(0xFF353637) +val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightMediumContrast = Color(0xFF6C6D6D) +val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val errorLightMediumContrast = Color(0xFF730012) +val onErrorLightMediumContrast = Color(0xFFFFFFFF) +val errorContainerLightMediumContrast = Color(0xFFC13F42) +val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) +val backgroundLightMediumContrast = Color(0xFFFCF8FF) +val onBackgroundLightMediumContrast = Color(0xFF1B1B23) +val surfaceLightMediumContrast = Color(0xFFFCF8F8) +val onSurfaceLightMediumContrast = Color(0xFF111111) +val surfaceVariantLightMediumContrast = Color(0xFFE0E3E3) +val onSurfaceVariantLightMediumContrast = Color(0xFF333738) +val outlineLightMediumContrast = Color(0xFF4F5354) +val outlineVariantLightMediumContrast = Color(0xFF6A6E6E) +val scrimLightMediumContrast = Color(0xFF000000) +val inverseSurfaceLightMediumContrast = Color(0xFF313030) +val inverseOnSurfaceLightMediumContrast = Color(0xFFF4F0EF) +val inversePrimaryLightMediumContrast = Color(0xFFC2C1FF) +val surfaceDimLightMediumContrast = Color(0xFFC9C6C5) +val surfaceBrightLightMediumContrast = Color(0xFFFCF8F8) +val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightMediumContrast = Color(0xFFF6F3F2) +val surfaceContainerLightMediumContrast = Color(0xFFEBE7E7) +val surfaceContainerHighLightMediumContrast = Color(0xFFDFDCDB) +val surfaceContainerHighestLightMediumContrast = Color(0xFFD4D1D0) + +val primaryLightHighContrast = Color(0xFF160299) +val onPrimaryLightHighContrast = Color(0xFFFFFFFF) +val primaryContainerLightHighContrast = Color(0xFF3835B4) +val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) +val secondaryLightHighContrast = Color(0xFF292B3D) +val onSecondaryLightHighContrast = Color(0xFFFFFFFF) +val secondaryContainerLightHighContrast = Color(0xFF46485C) +val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) +val tertiaryLightHighContrast = Color(0xFF2A2C2D) +val onTertiaryLightHighContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightHighContrast = Color(0xFF48494A) +val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) +val errorLightHighContrast = Color(0xFF60000D) +val onErrorLightHighContrast = Color(0xFFFFFFFF) +val errorContainerLightHighContrast = Color(0xFF8F1922) +val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) +val backgroundLightHighContrast = Color(0xFFFCF8FF) +val onBackgroundLightHighContrast = Color(0xFF1B1B23) +val surfaceLightHighContrast = Color(0xFFFCF8F8) +val onSurfaceLightHighContrast = Color(0xFF000000) +val surfaceVariantLightHighContrast = Color(0xFFE0E3E3) +val onSurfaceVariantLightHighContrast = Color(0xFF000000) +val outlineLightHighContrast = Color(0xFF292D2D) +val outlineVariantLightHighContrast = Color(0xFF464A4A) +val scrimLightHighContrast = Color(0xFF000000) +val inverseSurfaceLightHighContrast = Color(0xFF313030) +val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) +val inversePrimaryLightHighContrast = Color(0xFFC2C1FF) +val surfaceDimLightHighContrast = Color(0xFFBBB8B7) +val surfaceBrightLightHighContrast = Color(0xFFFCF8F8) +val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightHighContrast = Color(0xFFF4F0EF) +val surfaceContainerLightHighContrast = Color(0xFFE5E2E1) +val surfaceContainerHighLightHighContrast = Color(0xFFD7D4D3) +val surfaceContainerHighestLightHighContrast = Color(0xFFC9C6C5) + +val primaryDark = Color(0xFFC2C1FF) +val onPrimaryDark = Color(0xFF1B0E9D) +val primaryContainerDark = Color(0xFF8283FF) +val onPrimaryContainerDark = Color(0xFF12008E) +val secondaryDark = Color(0xFFFFFFFF) +val onSecondaryDark = Color(0xFF2D2F42) +val secondaryContainerDark = Color(0xFFE0E0F9) +val onSecondaryContainerDark = Color(0xFF616378) +val tertiaryDark = Color(0xFFFFFFFF) +val onTertiaryDark = Color(0xFF2F3131) +val tertiaryContainerDark = Color(0xFFE2E2E2) +val onTertiaryContainerDark = Color(0xFF636565) +val errorDark = Color(0xFFFFB3B0) +val onErrorDark = Color(0xFF68000F) +val errorContainerDark = Color(0xFFFF6C6C) +val onErrorContainerDark = Color(0xFF6E0011) +val backgroundDark = Color(0xFF13131A) +val onBackgroundDark = Color(0xFFE4E1EC) +val surfaceDark = Color(0xFF141313) +val onSurfaceDark = Color(0xFFE5E2E1) +val surfaceVariantDark = Color(0xFF444748) +val onSurfaceVariantDark = Color(0xFFC4C7C8) +val outlineDark = Color(0xFF8E9192) +val outlineVariantDark = Color(0xFF444748) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE5E2E1) +val inverseOnSurfaceDark = Color(0xFF313030) +val inversePrimaryDark = Color(0xFF4F4DCA) +val surfaceDimDark = Color(0xFF141313) +val surfaceBrightDark = Color(0xFF3A3939) +val surfaceContainerLowestDark = Color(0xFF0E0E0E) +val surfaceContainerLowDark = Color(0xFF1C1B1B) +val surfaceContainerDark = Color(0xFF201F1F) +val surfaceContainerHighDark = Color(0xFF2A2A2A) +val surfaceContainerHighestDark = Color(0xFF353434) + +val primaryDarkMediumContrast = Color(0xFFDBD9FF) +val onPrimaryDarkMediumContrast = Color(0xFF110088) +val primaryContainerDarkMediumContrast = Color(0xFF8283FF) +val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) +val secondaryDarkMediumContrast = Color(0xFFFFFFFF) +val onSecondaryDarkMediumContrast = Color(0xFF2D2F42) +val secondaryContainerDarkMediumContrast = Color(0xFFE0E0F9) +val onSecondaryContainerDarkMediumContrast = Color(0xFF45475A) +val tertiaryDarkMediumContrast = Color(0xFFFFFFFF) +val onTertiaryDarkMediumContrast = Color(0xFF2F3131) +val tertiaryContainerDarkMediumContrast = Color(0xFFE2E2E2) +val onTertiaryContainerDarkMediumContrast = Color(0xFF464848) +val errorDarkMediumContrast = Color(0xFFFFD2CF) +val onErrorDarkMediumContrast = Color(0xFF54000A) +val errorContainerDarkMediumContrast = Color(0xFFFF6C6C) +val onErrorContainerDarkMediumContrast = Color(0xFF250002) +val backgroundDarkMediumContrast = Color(0xFF13131A) +val onBackgroundDarkMediumContrast = Color(0xFFE4E1EC) +val surfaceDarkMediumContrast = Color(0xFF141313) +val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkMediumContrast = Color(0xFF444748) +val onSurfaceVariantDarkMediumContrast = Color(0xFFDADDDD) +val outlineDarkMediumContrast = Color(0xFFAFB2B3) +val outlineVariantDarkMediumContrast = Color(0xFF8D9191) +val scrimDarkMediumContrast = Color(0xFF000000) +val inverseSurfaceDarkMediumContrast = Color(0xFFE5E2E1) +val inverseOnSurfaceDarkMediumContrast = Color(0xFF2A2A2A) +val inversePrimaryDarkMediumContrast = Color(0xFF3733B3) +val surfaceDimDarkMediumContrast = Color(0xFF141313) +val surfaceBrightDarkMediumContrast = Color(0xFF454444) +val surfaceContainerLowestDarkMediumContrast = Color(0xFF070707) +val surfaceContainerLowDarkMediumContrast = Color(0xFF1E1D1D) +val surfaceContainerDarkMediumContrast = Color(0xFF282828) +val surfaceContainerHighDarkMediumContrast = Color(0xFF333232) +val surfaceContainerHighestDarkMediumContrast = Color(0xFF3E3D3D) + +val primaryDarkHighContrast = Color(0xFFF1EEFF) +val onPrimaryDarkHighContrast = Color(0xFF000000) +val primaryContainerDarkHighContrast = Color(0xFFBDBCFF) +val onPrimaryContainerDarkHighContrast = Color(0xFF03003B) +val secondaryDarkHighContrast = Color(0xFFFFFFFF) +val onSecondaryDarkHighContrast = Color(0xFF000000) +val secondaryContainerDarkHighContrast = Color(0xFFE0E0F9) +val onSecondaryContainerDarkHighContrast = Color(0xFF27293B) +val tertiaryDarkHighContrast = Color(0xFFFFFFFF) +val onTertiaryDarkHighContrast = Color(0xFF000000) +val tertiaryContainerDarkHighContrast = Color(0xFFE2E2E2) +val onTertiaryContainerDarkHighContrast = Color(0xFF282A2B) +val errorDarkHighContrast = Color(0xFFFFECEA) +val onErrorDarkHighContrast = Color(0xFF000000) +val errorContainerDarkHighContrast = Color(0xFFFFADAA) +val onErrorContainerDarkHighContrast = Color(0xFF220002) +val backgroundDarkHighContrast = Color(0xFF13131A) +val onBackgroundDarkHighContrast = Color(0xFFE4E1EC) +val surfaceDarkHighContrast = Color(0xFF141313) +val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkHighContrast = Color(0xFF444748) +val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF) +val outlineDarkHighContrast = Color(0xFFEEF0F1) +val outlineVariantDarkHighContrast = Color(0xFFC0C3C4) +val scrimDarkHighContrast = Color(0xFF000000) +val inverseSurfaceDarkHighContrast = Color(0xFFE5E2E1) +val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) +val inversePrimaryDarkHighContrast = Color(0xFF3733B3) +val surfaceDimDarkHighContrast = Color(0xFF141313) +val surfaceBrightDarkHighContrast = Color(0xFF51504F) +val surfaceContainerLowestDarkHighContrast = Color(0xFF000000) +val surfaceContainerLowDarkHighContrast = Color(0xFF201F1F) +val surfaceContainerDarkHighContrast = Color(0xFF313030) +val surfaceContainerHighDarkHighContrast = Color(0xFF3C3B3B) +val surfaceContainerHighestDarkHighContrast = Color(0xFF474646) + val White = Color(0xFFFFFFFF) val Black = Color(0xFF000000) diff --git a/app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt b/app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt index 9c55a3d..88e7334 100644 --- a/app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt +++ b/app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt @@ -12,31 +12,84 @@ import androidx.compose.ui.platform.LocalContext private val DarkColorScheme = darkColorScheme( primary = Main500, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, secondary = Main300, - tertiary = Main100 + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = Main100, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = BgBlack, + onBackground = onBackgroundDark, + surface = BgBlack, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, ) private val LightColorScheme = lightColorScheme( primary = Main500, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, secondary = Main300, - tertiary = Main100 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = Main100, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = BgWhite, + onBackground = onBackgroundLight, + surface = BgWhite, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, ) @Composable fun SampoomManagementTheme( darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, + dynamicColor: Boolean = false, content: @Composable () -> Unit ) { val colorScheme = when { diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt index 74ff84f..41ee5bd 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt @@ -9,9 +9,9 @@ import retrofit2.http.Body import retrofit2.http.POST interface AuthApi { - @POST("login") + @POST("auth/login") suspend fun login(@Body body: LoginRequestDto): ApiResponse - @POST("signup") + @POST("auth/signup") suspend fun signUp(@Body body: SignUpRequestDto): ApiResponse } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt index 36ed2a9..a894c8d 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt @@ -7,20 +7,16 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -35,6 +31,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sampoom.android.R import com.sampoom.android.core.ui.component.CommonButton import com.sampoom.android.core.ui.component.CommonTextField @@ -42,6 +39,7 @@ import com.sampoom.android.core.ui.theme.Main500 import com.sampoom.android.core.ui.component.ShowErrorSnackBar import com.sampoom.android.core.ui.component.rememberCommonSnackBarHostState import com.sampoom.android.core.ui.component.TopSnackBarHost +import com.sampoom.android.core.ui.theme.backgroundColor @Composable fun LoginScreen( @@ -57,15 +55,15 @@ fun LoginScreen( viewModel.bindLabel(emailLabel, passwordLabel, errorLabel) } - val state by viewModel.state.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() - LaunchedEffect(state.success) { - if (state.success) onSuccess() + LaunchedEffect(uiState.success) { + if (uiState.success) onSuccess() } val snackBarHostState = rememberCommonSnackBarHostState() ShowErrorSnackBar( - errorMessage = state.error, + errorMessage = uiState.error, snackBarHostState = snackBarHostState, onConsumed = { viewModel.consumeError() } ) @@ -101,30 +99,30 @@ fun LoginScreen( Spacer(Modifier.height(48.dp)) CommonTextField( modifier = Modifier.fillMaxWidth(), - value = state.email, + value = uiState.email, onValueChange = { viewModel.onEvent(LoginUiEvent.EmailChanged(it)) }, placeholder = stringResource(R.string.login_placeholder_email), - isError = state.emailError != null, - errorMessage = state.emailError + isError = uiState.emailError != null, + errorMessage = uiState.emailError ) Spacer(Modifier.height(8.dp)) CommonTextField( modifier = Modifier.fillMaxWidth(), - value = state.password, + value = uiState.password, onValueChange = { viewModel.onEvent(LoginUiEvent.PasswordChanged(it)) }, placeholder = stringResource(R.string.login_placeholder_password), isPassword = true, - isError = state.passwordError != null, - errorMessage = state.passwordError + isError = uiState.passwordError != null, + errorMessage = uiState.passwordError ) Spacer(Modifier.height(48.dp)) CommonButton( onClick = { viewModel.onEvent(LoginUiEvent.Submit) }, - enabled = state.isValid && !state.loading, + enabled = uiState.isValid && !uiState.loading, modifier = Modifier.fillMaxWidth() ) { Text( - if (state.loading) stringResource(R.string.login_button_login_loading) + if (uiState.loading) stringResource(R.string.login_button_login_loading) else stringResource(R.string.login_button_login) ) } diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt index 997312c..83ba328 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt @@ -20,8 +20,8 @@ class LoginViewModel @Inject constructor( private val singIn: LoginUseCase, private val application: Application ) : ViewModel() { - private val _state = MutableStateFlow(LoginUiState()) - val state: StateFlow = _state + private val _uiState = MutableStateFlow(LoginUiState()) + val uiState: StateFlow = _uiState private var emailLabel: String = "" private var passwordLabel: String = "" @@ -35,26 +35,26 @@ class LoginViewModel @Inject constructor( fun onEvent(e: LoginUiEvent) = when (e) { is LoginUiEvent.EmailChanged -> { - _state.value = _state.value.copy(email = e.email) + _uiState.value = _uiState.value.copy(email = e.email) validateEmail() } is LoginUiEvent.PasswordChanged -> { - _state.value = _state.value.copy(password = e.password) + _uiState.value = _uiState.value.copy(password = e.password) validatePassword() } LoginUiEvent.Submit -> submit() } private fun validateEmail() { - val result = AuthValidator.validateNotEmpty(_state.value.email, emailLabel) - _state.value = _state.value.copy( + val result = AuthValidator.validateNotEmpty(_uiState.value.email, emailLabel) + _uiState.value = _uiState.value.copy( emailError = result.toErrorMessage() ) } private fun validatePassword() { - val result = AuthValidator.validateNotEmpty(_state.value.password, passwordLabel) - _state.value = _state.value.copy( + val result = AuthValidator.validateNotEmpty(_uiState.value.password, passwordLabel) + _uiState.value = _uiState.value.copy( passwordError = result.toErrorMessage() ) } @@ -71,22 +71,22 @@ class LoginViewModel @Inject constructor( validateEmail() validatePassword() - if (!_state.value.isValid) return@launch + if (!_uiState.value.isValid) return@launch - val s = _state.value - _state.update { it.copy(loading = true, error = null) } + val s = _uiState.value + _uiState.update { it.copy(loading = true, error = null) } runCatching { singIn(s.email, s.password) } - .onSuccess { _state.update { it.copy(loading = false, success = true) } } + .onSuccess { _uiState.update { it.copy(loading = false, success = true) } } .onFailure { throwable -> val backendMessage = throwable.serverMessageOrNull() - _state.update { + _uiState.update { it.copy(loading = false, error = backendMessage ?: (throwable.message ?: errorLabel)) } } - Log.d("LoginViewModel", "submit: ${_state.value}") + Log.d("LoginViewModel", "submit: ${_uiState.value}") } fun consumeError() { - _state.update { it.copy(error = null) } + _uiState.update { it.copy(error = null) } } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt index 172037c..b252289 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt @@ -1,6 +1,7 @@ package com.sampoom.android.feature.auth.ui import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -41,6 +42,7 @@ import com.sampoom.android.core.ui.component.CommonTextField import com.sampoom.android.core.ui.component.ShowErrorSnackBar import com.sampoom.android.core.ui.component.rememberCommonSnackBarHostState import com.sampoom.android.core.ui.component.TopSnackBarHost +import com.sampoom.android.core.ui.theme.backgroundColor @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/local/database/.gitkeep b/app/src/main/java/com/sampoom/android/feature/part/data/local/database/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/local/preferences/.gitkeep b/app/src/main/java/com/sampoom/android/feature/part/data/local/preferences/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt b/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt index 0c06f6a..65800a6 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt @@ -1,6 +1,12 @@ package com.sampoom.android.feature.part.data.mapper +import com.sampoom.android.feature.part.data.remote.dto.CategoryDto +import com.sampoom.android.feature.part.data.remote.dto.GroupDto import com.sampoom.android.feature.part.data.remote.dto.PartDto +import com.sampoom.android.feature.part.domain.model.Category +import com.sampoom.android.feature.part.domain.model.Group import com.sampoom.android.feature.part.domain.model.Part -fun PartDto.toModel(): Part = Part(id, name, count) \ No newline at end of file +fun CategoryDto.toModel(): Category = Category(id, code, name) +fun GroupDto.toModel(): Group = Group(id, code, name, categoryId) +fun PartDto.toModel(): Part = Part(partId, code, name, quantity) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/remote/api/PartApi.kt b/app/src/main/java/com/sampoom/android/feature/part/data/remote/api/PartApi.kt index e5a4998..06f1004 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/remote/api/PartApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/remote/api/PartApi.kt @@ -1,10 +1,19 @@ package com.sampoom.android.feature.part.data.remote.api import com.sampoom.android.core.network.ApiResponse +import com.sampoom.android.feature.part.data.remote.dto.CategoryDto +import com.sampoom.android.feature.part.data.remote.dto.GroupDto import com.sampoom.android.feature.part.data.remote.dto.PartDto import retrofit2.http.GET +import retrofit2.http.Path interface PartApi { - @GET("part") - suspend fun getPartList(): ApiResponse> + @GET("agency/category") + suspend fun getCategoryList(): ApiResponse> + + @GET("agency/category/{categoryId}") + suspend fun getGroupList(@Path("categoryId") categoryId: Long): ApiResponse> + + @GET("agency/1/group/{groupId}") + suspend fun getPartList(@Path("groupId") groupId: Long): ApiResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/CategoryDto.kt b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/CategoryDto.kt new file mode 100644 index 0000000..313b6cb --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/CategoryDto.kt @@ -0,0 +1,7 @@ +package com.sampoom.android.feature.part.data.remote.dto + +data class CategoryDto( + val id: Long, + val code: String, + val name: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/GroupDto.kt b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/GroupDto.kt new file mode 100644 index 0000000..991e1fe --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/GroupDto.kt @@ -0,0 +1,8 @@ +package com.sampoom.android.feature.part.data.remote.dto + +data class GroupDto( + val id: Long, + val code: String, + val name: String, + val categoryId: Long +) diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt index d7973be..41872f7 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt @@ -1,7 +1,8 @@ package com.sampoom.android.feature.part.data.remote.dto data class PartDto( - val id: Int, + val partId: Long, + val code: String, val name: String, - val count: Int + val quantity: Long ) diff --git a/app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt index 663cd0a..f40c510 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt @@ -2,6 +2,8 @@ package com.sampoom.android.feature.part.data.repository import com.sampoom.android.feature.part.data.mapper.toModel import com.sampoom.android.feature.part.data.remote.api.PartApi +import com.sampoom.android.feature.part.domain.model.CategoryList +import com.sampoom.android.feature.part.domain.model.GroupList import com.sampoom.android.feature.part.domain.model.PartList import com.sampoom.android.feature.part.domain.repository.PartRepository import javax.inject.Inject @@ -9,8 +11,20 @@ import javax.inject.Inject class PartRepositoryImpl @Inject constructor( private val api: PartApi ) : PartRepository { - override suspend fun getPartList(): PartList { - val response = api.getPartList() + override suspend fun getCategoryList(): CategoryList { + val dto = api.getCategoryList() + val categoryItems = dto.data.map { it.toModel() } + return CategoryList(items = categoryItems) + } + + override suspend fun getGroupList(categoryId: Long): GroupList { + val response = api.getGroupList(categoryId) + val groupItems = response.data.map { it.toModel() } + return GroupList(items = groupItems) + } + + override suspend fun getPartList(groupId: Long): PartList { + val response = api.getPartList(groupId) val partItems = response.data.map { it.toModel() } return PartList(items = partItems) } diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Category.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Category.kt new file mode 100644 index 0000000..18834d6 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Category.kt @@ -0,0 +1,7 @@ +package com.sampoom.android.feature.part.domain.model + +data class Category( + val id: Long, + val code: String, + val name: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/CategoryList.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/CategoryList.kt new file mode 100644 index 0000000..95d8248 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/CategoryList.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.part.domain.model + +data class CategoryList( + val items: List, + val totalCount: Int = items.size, + val isEmpty: Boolean = items.isEmpty() +) { + companion object Companion { + fun empty() = CategoryList(emptyList()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Group.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Group.kt new file mode 100644 index 0000000..833c004 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Group.kt @@ -0,0 +1,8 @@ +package com.sampoom.android.feature.part.domain.model + +data class Group( + val id: Long, + val code: String, + val name: String, + val categoryId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/GroupList.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/GroupList.kt new file mode 100644 index 0000000..34bdd22 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/GroupList.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.part.domain.model + +data class GroupList( + val items: List, + val totalCount: Int = items.size, + val isEmpty: Boolean = items.isEmpty() +) { + companion object Companion { + fun empty() = GroupList(emptyList()) + } +} diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt index 9c1bb23..019b63b 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt @@ -1,7 +1,8 @@ package com.sampoom.android.feature.part.domain.model data class Part( - val id: Int, + val partId: Long, + val code: String, val name: String, - val count: Int -) \ No newline at end of file + val quantity: Long +) diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt index 06c46d5..3d761e0 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt @@ -1,7 +1,11 @@ package com.sampoom.android.feature.part.domain.repository +import com.sampoom.android.feature.part.domain.model.CategoryList +import com.sampoom.android.feature.part.domain.model.GroupList import com.sampoom.android.feature.part.domain.model.PartList interface PartRepository { - suspend fun getPartList(): PartList + suspend fun getCategoryList(): CategoryList + suspend fun getGroupList(categoryId: Long): GroupList + suspend fun getPartList(groupId: Long): PartList } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetCategoryUseCase.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetCategoryUseCase.kt new file mode 100644 index 0000000..3029262 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetCategoryUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.part.domain.usecase + +import com.sampoom.android.feature.part.domain.model.CategoryList +import com.sampoom.android.feature.part.domain.repository.PartRepository +import javax.inject.Inject + +class GetCategoryUseCase @Inject constructor( + private val repository: PartRepository +) { + suspend operator fun invoke(): CategoryList = repository.getCategoryList() +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetGroupUseCase.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetGroupUseCase.kt new file mode 100644 index 0000000..256be21 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetGroupUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.part.domain.usecase + +import com.sampoom.android.feature.part.domain.model.GroupList +import com.sampoom.android.feature.part.domain.repository.PartRepository +import javax.inject.Inject + +class GetGroupUseCase @Inject constructor( + private val repository: PartRepository +) { + suspend operator fun invoke(categoryId: Long): GroupList = repository.getGroupList(categoryId) +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt index 9fdd394..93ca316 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt @@ -7,5 +7,5 @@ import javax.inject.Inject class GetPartUseCase @Inject constructor( private val repository: PartRepository ) { - suspend operator fun invoke(): PartList = repository.getPartList() + suspend operator fun invoke(groupId: Long): PartList = repository.getPartList(groupId) } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt new file mode 100644 index 0000000..61f6997 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt @@ -0,0 +1,168 @@ +package com.sampoom.android.feature.part.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sampoom.android.R +import com.sampoom.android.core.ui.theme.backgroundCardColor +import com.sampoom.android.core.ui.theme.textColor +import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.feature.part.domain.model.Part + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PartListScreen( + onNavigateBack: () -> Unit = {}, + viewModel: PartListViewModel = hiltViewModel() +) { + val errorLabel = stringResource(R.string.common_error) + + LaunchedEffect(errorLabel) { + viewModel.bindLabel(errorLabel) + } + + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.part_title)) }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + painter = painterResource(R.drawable.ic_arrow_back), + contentDescription = stringResource(R.string.nav_back) + ) + } + } + ) + } + ) { innerPadding -> + when { + uiState.partListLoading -> { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + uiState.partListError != null -> { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ErrorContent( + onRetry = { viewModel.onEvent(PartListUiEvent.RetryPartList) }, + modifier = Modifier.height(200.dp) + ) + } + } + + uiState.partList.isEmpty() -> { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + EmptyContent( + message = stringResource(R.string.part_empty_part), + modifier = Modifier.height(200.dp) + ) + } + } + + else -> { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(uiState.partList) { part -> + PartListItemCard(part = part) + } + } + } + } + } +} + +@Composable +private fun PartListItemCard( + part: Part +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1F) + ) { + Text( + text = part.name, + color = textColor(), + style = MaterialTheme.typography.titleMedium + ) + Text( + text = part.code, + color = textSecondaryColor(), + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Light + ) + } + + Text( + text = part.quantity.toString(), + color = textColor(), + style = MaterialTheme.typography.titleMedium + ) + + Icon( + painterResource(R.drawable.chevron_right), + contentDescription = stringResource(R.string.common_detail), + tint = textSecondaryColor() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiEvent.kt new file mode 100644 index 0000000..8026605 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiEvent.kt @@ -0,0 +1,6 @@ +package com.sampoom.android.feature.part.ui + +sealed interface PartListUiEvent { + object LoadPartList : PartListUiEvent + object RetryPartList : PartListUiEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiState.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiState.kt new file mode 100644 index 0000000..e5bbee1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiState.kt @@ -0,0 +1,9 @@ +package com.sampoom.android.feature.part.ui + +import com.sampoom.android.feature.part.domain.model.Part + +data class PartListUiState( + val partList: List = emptyList(), + val partListLoading: Boolean = false, + val partListError: String? = null +) diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt new file mode 100644 index 0000000..999d8a9 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt @@ -0,0 +1,72 @@ +package com.sampoom.android.feature.part.ui + +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sampoom.android.core.network.serverMessageOrNull +import com.sampoom.android.feature.part.domain.usecase.GetPartUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PartListViewModel @Inject constructor( + private val getPartListUseCase: GetPartUseCase, + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val _uiState = MutableStateFlow(PartListUiState()) + val uiState: StateFlow = _uiState + + // Navigation 인자 로드 + private val agencyId: Long = savedStateHandle.get("agencyId") ?: 0L + private val groupId: Long = savedStateHandle.get("groupId") ?: 0L + + private var errorLabel: String = "" + + fun bindLabel(error: String) { + errorLabel = error + } + + init { + if (groupId > 0L) loadPartList(groupId) + else _uiState.update { it.copy(partListError = errorLabel) } + } + + fun onEvent(event: PartListUiEvent) { + when (event) { + is PartListUiEvent.LoadPartList -> loadPartList(groupId) + is PartListUiEvent.RetryPartList -> loadPartList(groupId) + } + } + + private fun loadPartList(groupId: Long) { + viewModelScope.launch { + _uiState.update { it.copy(partListLoading = true, partListError = null) } + + runCatching { getPartListUseCase(groupId) } + .onSuccess { partList -> + _uiState.update { + it.copy( + partList = partList.items, + partListLoading = false, + partListError = null + ) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + _uiState.update { + it.copy( + partListLoading = false, + partListError = backendMessage ?: (throwable.message ?: errorLabel) + ) + } + } + Log.d("PartListViewModel", "submit: ${_uiState.value}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt index 1164887..97ae617 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt @@ -1,96 +1,306 @@ package com.sampoom.android.feature.part.ui import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.* import androidx.compose.runtime.* 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.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sampoom.android.R +import com.sampoom.android.core.ui.component.EmptyContent +import com.sampoom.android.core.ui.component.ErrorContent +import com.sampoom.android.core.ui.theme.Main500 +import com.sampoom.android.core.ui.theme.White +import com.sampoom.android.core.ui.theme.backgroundCardColor +import com.sampoom.android.core.ui.theme.textColor +import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.feature.part.domain.model.Category +import com.sampoom.android.feature.part.domain.model.Group +@OptIn(ExperimentalMaterial3Api::class) @Composable fun PartScreen( + onNavigateBack: () -> Unit = {}, + onNavigatePartList: (Group) -> Unit, viewModel: PartViewModel = hiltViewModel() ) { + val errorLabel = stringResource(R.string.common_error) + + LaunchedEffect(errorLabel) { + viewModel.bindLabel(errorLabel) + } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() - Scaffold { innerPadding -> + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.part_title)) }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + painter = painterResource(R.drawable.ic_arrow_back), + contentDescription = stringResource(R.string.nav_back) + ) + } + } + ) + } + ) { innerPadding -> Column( modifier = Modifier .fillMaxSize() .padding(innerPadding) - .padding(16.dp) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) ) { + Spacer(Modifier.height(16.dp)) + + // 검색 바 + OutlinedTextField( + value = "uiState.searchQuery", + onValueChange = { "viewModel.onSearchQueryChanged(it)" }, + placeholder = { Text(stringResource(R.string.part_placeholder_search)) }, + trailingIcon = { + Icon( + Icons.Default.Search, + contentDescription = stringResource(R.string.part_title_search), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + shape = RoundedCornerShape(24.dp), + singleLine = true, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), + ) + ) + + // Category 선택 제목 Text( - text = stringResource(R.string.part_title), - style = MaterialTheme.typography.headlineMedium, + text = stringResource(R.string.part_title_category), + style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(vertical = 16.dp) ) + // Category 섹션 when { - uiState.loading -> { + uiState.categoryLoading -> { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxWidth() + .height(200.dp), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } - uiState.error != null -> { + uiState.categoryError != null -> { + ErrorContent( + onRetry = { viewModel.onEvent(PartUiEvent.RetryCategories) }, + modifier = Modifier.height(200.dp) + ) + } + + uiState.categoryList.isEmpty() -> { + EmptyContent( + message = stringResource(R.string.part_empty_category), + modifier = Modifier.height(200.dp) + ) + } + + else -> { + // 2x3 그리드로 카테고리 배치 Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - text = "${stringResource(R.string.common_error)}: ${uiState.error}", - color = MaterialTheme.colorScheme.error - ) - Spacer(modifier = Modifier.height(16.dp)) - Button(onClick = { viewModel.refreshPart() }) { - Text(stringResource((R.string.common_retry))) + uiState.categoryList.chunked(3).forEach { categoryChunk -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + categoryChunk.forEach { category -> + CategoryItem( + category = category, + isSelected = category.id == uiState.selectedCategory?.id, + onClick = { + viewModel.onEvent( + PartUiEvent.CategorySelected( + category + ) + ) + }, + modifier = Modifier.weight(1f) + ) + } + + // 3개 미만인 경우 빈 공간 채우기 + repeat(3 - categoryChunk.size) { + Spacer(modifier = Modifier.weight(1f)) + } + } } } } + } - uiState.partList.isEmpty() -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text(stringResource(R.string.part_empty)) - } + Spacer(Modifier.height(24.dp)) + + // 그룹 리스트 섹션 + if (uiState.selectedCategory == null) { + // 초기 상태: 카테고리 선택 안내 + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(R.drawable.search), + contentDescription = null, + modifier = Modifier.size(32.dp), + tint = textSecondaryColor() + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.part_select_category), + style = MaterialTheme.typography.bodyMedium, + color = textSecondaryColor() + ) } + } else { + // 그룹 선택 제목 + Text( + text = stringResource(R.string.part_title_group), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(vertical = 16.dp) + ) - else -> { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(uiState.partList) { inventory -> - PartItemCard(part = inventory) + // 그룹 리스트 + when { + uiState.groupLoading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + uiState.groupError != null -> { + ErrorContent( + onRetry = { viewModel.onEvent(PartUiEvent.RetryGroups) }, + modifier = Modifier.height(200.dp) + ) + } + + uiState.groupList.isEmpty() -> { + EmptyContent( + message = stringResource(R.string.part_empty_group), + modifier = Modifier.height(200.dp) + ) + } + + else -> { + Column( + Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + uiState.groupList.forEach { group -> + PartItemCard( + group = group, + onClick = { onNavigatePartList(group) } + ) + } } } } } + + Spacer(Modifier.height(32.dp)) } } } +// Category 아이템 +@Composable +fun CategoryItem( + category: Category, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + onClick = { onClick() }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = if (isSelected) Main500 else backgroundCardColor() + ), + modifier = modifier.height(100.dp) + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(id = resourceMapper(category.code)), + contentDescription = category.name, + modifier = Modifier.size(32.dp), + tint = if (isSelected) White else textColor() + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = category.name, + style = MaterialTheme.typography.bodySmall, + color = if (isSelected) White else textColor() + ) + } + } +} + +private fun resourceMapper(code: String): Int { + return when (code) { + "ENG" -> R.drawable.engine + "TRN" -> R.drawable.transmission + "CHS" -> R.drawable.chassis + "BDY" -> R.drawable.body + "TRM" -> R.drawable.trim + "ELE" -> R.drawable.electric + else -> R.drawable.parts + } +} + @Composable private fun PartItemCard( - part: com.sampoom.android.feature.part.domain.model.Part + group: Group, + onClick: () -> Unit ) { Card( + onClick = { onClick() }, modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + colors = CardDefaults.cardColors(containerColor = backgroundCardColor()), ) { Row( modifier = Modifier @@ -99,24 +309,14 @@ private fun PartItemCard( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Column { - Text( - text = part.name, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Text( - text = "ID: ${part.id}", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - Text( - text = "${part.count}개", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary + text = group.name, + style = MaterialTheme.typography.titleMedium + ) + + Icon( + painterResource(R.drawable.chevron_right), + contentDescription = stringResource(R.string.common_detail) ) } } diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt index e6a49d0..7dbf889 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt @@ -1,4 +1,10 @@ package com.sampoom.android.feature.part.ui +import com.sampoom.android.feature.part.domain.model.Category + sealed interface PartUiEvent { + object LoadCategories : PartUiEvent + data class CategorySelected(val category: Category) : PartUiEvent + object RetryCategories : PartUiEvent + object RetryGroups : PartUiEvent } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt index d343dbe..f43a4f6 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt @@ -1,10 +1,19 @@ package com.sampoom.android.feature.part.ui -import com.sampoom.android.feature.part.domain.model.Part +import com.sampoom.android.feature.part.domain.model.Category +import com.sampoom.android.feature.part.domain.model.Group data class PartUiState( - val loading: Boolean = false, - val error: String? = null, - val success: Boolean = false, - val partList: List = emptyList() + // Part + val groupList: List = emptyList(), + val groupLoading: Boolean = false, + val groupError: String? = null, + + // 선택된 Category + val selectedCategory: Category? = null, + + // Category + val categoryList: List = emptyList(), + val categoryLoading: Boolean = false, + val categoryError: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt b/app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt index c979584..6bcf62a 100644 --- a/app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt @@ -1,47 +1,122 @@ package com.sampoom.android.feature.part.ui +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.sampoom.android.feature.part.domain.usecase.GetPartUseCase +import com.sampoom.android.core.network.serverMessageOrNull +import com.sampoom.android.feature.part.domain.model.Category +import com.sampoom.android.feature.part.domain.usecase.GetCategoryUseCase +import com.sampoom.android.feature.part.domain.usecase.GetGroupUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class PartViewModel @Inject constructor( - private val getPartUseCase: GetPartUseCase + private val getCategoryUseCase: GetCategoryUseCase, + private val getGroupUseCase: GetGroupUseCase ) : ViewModel() { - private val _uiState = MutableStateFlow(PartUiState()) - val uiState: StateFlow = _uiState.asStateFlow() + val uiState: StateFlow = _uiState + + private var groupLoadJob: Job? = null + private var errorLabel: String = "" + + fun bindLabel(error: String) { + errorLabel = error + } init { - loadPart() + loadCategory() + } + + fun onEvent(event: PartUiEvent) { + when (event) { + is PartUiEvent.LoadCategories -> loadCategory() + is PartUiEvent.CategorySelected -> selectCategory(event.category) + is PartUiEvent.RetryCategories -> loadCategory() + is PartUiEvent.RetryGroups -> loadGroup() + } + } + + private fun loadCategory() { + viewModelScope.launch { + _uiState.update { it.copy(categoryLoading = true, categoryError = null) } + + runCatching { getCategoryUseCase() } + .onSuccess { categoryList -> + _uiState.update { + it.copy( + categoryList = categoryList.items, + categoryLoading = false, + categoryError = null + ) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + _uiState.update { + it.copy( + categoryLoading = false, + categoryError = backendMessage ?: (throwable.message ?: errorLabel) + ) + } + } + Log.d("PartViewModel", "submit: ${_uiState.value}") + } } - private fun loadPart() { + private fun selectCategory(category: Category) { viewModelScope.launch { - _uiState.value = _uiState.value.copy(loading = true, error = null) - try { - val partListResult = getPartUseCase() - _uiState.value = _uiState.value.copy( - loading = false, - partList = partListResult.items, - success = true - ) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - loading = false, - error = e.message ?: "Unknown error occurred" - ) - } + _uiState.update { it.copy(selectedCategory = category) } + groupLoadJob?.cancel() // 기존 그룹 로드 취소 후 새 요청 + loadGroup(category.id) } } - fun refreshPart() { - loadPart() + private fun loadGroup(categoryId: Long) { + groupLoadJob?.cancel() + groupLoadJob = viewModelScope.launch { + _uiState.update { it.copy(groupLoading = true, groupError = null) } + + runCatching { getGroupUseCase(categoryId) } + .onSuccess { groupList -> + // 최신 선택과 불일치하면 무시 + if (_uiState.value.selectedCategory?.id != categoryId) return@onSuccess + _uiState.update { + it.copy( + groupList = groupList.items, + groupLoading = false, + groupError = null + ) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + if (_uiState.value.selectedCategory?.id != categoryId) return@onFailure + _uiState.update { + it.copy( + groupLoading = false, + groupError = backendMessage ?: (throwable.message ?: errorLabel) + ) + } + } + Log.d("PartViewModel", "submit: ${_uiState.value}") + } } + + private fun loadGroup() { + val selectedCategory = _uiState.value.selectedCategory + if (selectedCategory != null) { + loadGroup(selectedCategory.id) + } + } + +// fun refreshPart() { +// loadCategory() +// } } \ No newline at end of file diff --git a/app/src/main/res/drawable/body.xml b/app/src/main/res/drawable/body.xml new file mode 100644 index 0000000..3ddac4e --- /dev/null +++ b/app/src/main/res/drawable/body.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/cart.xml b/app/src/main/res/drawable/cart.xml index afc4912..12a29bd 100644 --- a/app/src/main/res/drawable/cart.xml +++ b/app/src/main/res/drawable/cart.xml @@ -8,6 +8,6 @@ android:pathData="M0.077,0h24.077v24.333h-24.077z"/> + android:fillColor="@color/text"/> diff --git a/app/src/main/res/drawable/chassis.xml b/app/src/main/res/drawable/chassis.xml new file mode 100644 index 0000000..b73b9dd --- /dev/null +++ b/app/src/main/res/drawable/chassis.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/chevron_right.xml b/app/src/main/res/drawable/chevron_right.xml new file mode 100644 index 0000000..6c71c89 --- /dev/null +++ b/app/src/main/res/drawable/chevron_right.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/dashboard.xml b/app/src/main/res/drawable/dashboard.xml index c2c0e05..f7b65e4 100644 --- a/app/src/main/res/drawable/dashboard.xml +++ b/app/src/main/res/drawable/dashboard.xml @@ -5,5 +5,5 @@ android:viewportHeight="25"> + android:fillColor="@color/text"/> diff --git a/app/src/main/res/drawable/delivery.xml b/app/src/main/res/drawable/delivery.xml index ca411d4..9069781 100644 --- a/app/src/main/res/drawable/delivery.xml +++ b/app/src/main/res/drawable/delivery.xml @@ -8,6 +8,6 @@ android:pathData="M0.615,0h24v24h-24z"/> + android:fillColor="@color/text"/> diff --git a/app/src/main/res/drawable/electric.xml b/app/src/main/res/drawable/electric.xml new file mode 100644 index 0000000..223c5da --- /dev/null +++ b/app/src/main/res/drawable/electric.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/engine.xml b/app/src/main/res/drawable/engine.xml new file mode 100644 index 0000000..3785215 --- /dev/null +++ b/app/src/main/res/drawable/engine.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/orders.xml b/app/src/main/res/drawable/orders.xml index 7521789..41a7732 100644 --- a/app/src/main/res/drawable/orders.xml +++ b/app/src/main/res/drawable/orders.xml @@ -5,5 +5,5 @@ android:viewportHeight="25"> + android:fillColor="@color/text"/> diff --git a/app/src/main/res/drawable/parts.xml b/app/src/main/res/drawable/parts.xml index ff89964..f55ed30 100644 --- a/app/src/main/res/drawable/parts.xml +++ b/app/src/main/res/drawable/parts.xml @@ -5,5 +5,5 @@ android:viewportHeight="25"> + android:fillColor="@color/text"/> diff --git a/app/src/main/res/drawable/search.xml b/app/src/main/res/drawable/search.xml index 519d2d6..c8582cb 100644 --- a/app/src/main/res/drawable/search.xml +++ b/app/src/main/res/drawable/search.xml @@ -8,6 +8,6 @@ android:strokeLineJoin="round" android:strokeWidth="2" android:fillColor="#00000000" - android:strokeColor="#36393F" + android:strokeColor="@color/text" android:strokeLineCap="round"/> diff --git a/app/src/main/res/drawable/transmission.xml b/app/src/main/res/drawable/transmission.xml new file mode 100644 index 0000000..0af0ab7 --- /dev/null +++ b/app/src/main/res/drawable/transmission.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/trim.xml b/app/src/main/res/drawable/trim.xml new file mode 100644 index 0000000..ffb4a25 --- /dev/null +++ b/app/src/main/res/drawable/trim.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5de1348..7f7d2ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,14 +37,22 @@ 회원가입 회원 가입 중… - - 부품 목록 - 부품 목록이 없습니다. + + 부품조회 + 검색 + 카테고리 선택 + 그룹 선택 + 부품명으로 검색 + 카테고리 목록이 없습니다. + 부품 목록이 없습니다. + 카테고리를 선택해주세요. + 그룹 목록이 없습니다. 오류가 발생했습니다 다시 시도 닫기 + 상세 보기 이메일을 입력해주세요