From a89a5f26ffade29ad42d85a91a2c1927e54a24a8 Mon Sep 17 00:00:00 2001 From: Sangyoon Date: Mon, 10 Nov 2025 17:16:15 +0900 Subject: [PATCH] =?UTF-8?q?[REFAC]=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= =?UTF-8?q?=20=EB=B0=8F=20=ED=86=A0=ED=81=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../android/app/navigation/AppNavHost.kt | 37 ++++++++++++++++++- .../sampoom/android/core/di/NetworkModule.kt | 10 +++-- .../android/core/network/ErrorHandling.kt | 1 + .../core/network/TokenAuthenticator.kt | 9 ++++- .../android/core/network/TokenInterceptor.kt | 22 +++++++++-- .../core/network/TokenLogoutEmitter.kt | 17 +++++++++ .../core/network/TokenRefreshService.kt | 1 + .../core/preferences/AuthPreferences.kt | 9 +++++ .../android/core/ui/component/CommonButton.kt | 1 + .../feature/auth/data/remote/api/AuthApi.kt | 6 +++ .../data/repository/AuthRepositoryImpl.kt | 7 ++++ .../android/feature/auth/ui/AuthViewModel.kt | 7 +++- .../android/feature/auth/ui/LoginScreen.kt | 3 +- .../android/feature/auth/ui/LoginViewModel.kt | 8 ++-- .../android/feature/auth/ui/SignUpScreen.kt | 28 +++++++------- .../android/feature/auth/ui/SignUpUiEvent.kt | 1 - .../feature/auth/ui/SignUpViewModel.kt | 8 +--- .../data/repository/CartRepositoryImpl.kt | 5 +++ .../android/feature/cart/ui/CartListScreen.kt | 2 + .../repository/DashboardRepositoryImpl.kt | 2 + .../feature/dashboard/ui/DashboardScreen.kt | 16 +++++++- .../feature/dashboard/ui/SettingScreen.kt | 2 + .../data/repository/OrderRepositoryImpl.kt | 6 +++ .../feature/order/ui/OrderDetailContent.kt | 7 +++- .../android/feature/order/ui/OrderItem.kt | 1 + .../order/ui/OrderResultBottomSheet.kt | 2 + .../data/repository/OutboundRepositoryImpl.kt | 6 +++ .../feature/outbound/ui/OutboundListScreen.kt | 2 + .../feature/part/data/remote/api/PartApi.kt | 4 ++ .../data/repository/PartRepositoryImpl.kt | 4 ++ .../android/feature/part/ui/PartListScreen.kt | 1 + .../android/feature/part/ui/PartScreen.kt | 6 ++- .../feature/user/data/remote/api/UserApi.kt | 5 +++ .../data/repository/UserRepositoryImpl.kt | 7 ++++ .../feature/user/ui/EmployeeListScreen.kt | 2 - 36 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/sampoom/android/core/network/TokenLogoutEmitter.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 91229f4..b363b44 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,8 +32,8 @@ android { applicationId = "com.sampoom.android" minSdk = 26 targetSdk = 36 - versionCode = 4 - versionName = "1.0.3" + versionCode = 5 + versionName = "1.0.4" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } 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 44eb2fb..2e81678 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 @@ -116,6 +116,16 @@ fun AppNavHost( } } + LaunchedEffect(Unit) { + authViewModel.logoutEvent.collect { + navController.navigate(ROUTE_LOGIN) { + popUpTo(navController.graph.startDestinationId) { inclusive = true } + launchSingleTop = true + } + } + } + + // 앱 로그인 도중 로딩 상태 표시로 화면 깜빡임 제거 if (isLoading) { Box( modifier = Modifier.fillMaxSize(), @@ -128,22 +138,24 @@ fun AppNavHost( NavHost( navController = navController, -// startDestination = ROUTE_HOME, startDestination = if (isLoggedIn) ROUTE_HOME else ROUTE_LOGIN, modifier = Modifier.background(backgroundColor()) ) { + // 로그인 composable(ROUTE_LOGIN) { LoginScreen( onSuccess = { authViewModel.updateLoginState() navController.navigate(ROUTE_HOME) { - popUpTo(ROUTE_LOGIN) { inclusive = true } // 로그인 화면 스택 제거 + popUpTo(ROUTE_LOGIN) { inclusive = true } } }, onNavigateSignUp = { navController.navigate(ROUTE_SIGNUP) }) } + + // 회원가입 composable(ROUTE_SIGNUP) { SignUpScreen( onSuccess = { @@ -156,7 +168,11 @@ fun AppNavHost( } ) } + + // 홈 composable(ROUTE_HOME) { MainScreen(navController, user) } + + // 부품 조회 composable(ROUTE_PARTS) { PartScreen( onNavigateBack = { @@ -169,6 +185,8 @@ fun AppNavHost( } ) } + + // 부품 리스트 조회 composable( ROUTE_PART_LIST, arguments = listOf( @@ -183,6 +201,8 @@ fun AppNavHost( navController = navController ) } + + // 주문 상세 composable( ROUTE_ORDER_DETAIL, arguments = listOf( @@ -196,6 +216,8 @@ fun AppNavHost( } ) } + + // 설정 composable( ROUTE_SETTINGS ) { @@ -212,6 +234,8 @@ fun AppNavHost( } ) } + + // 직원 관리 composable(ROUTE_EMPLOYEE) { EmployeeListScreen( onNavigateBack = { @@ -238,6 +262,7 @@ fun MainScreen( navController = navController, startDestination = ROUTE_DASHBOARD ) { + // 대시보드 composable(ROUTE_DASHBOARD) { DashboardScreen( paddingValues = innerPadding, @@ -260,16 +285,22 @@ fun MainScreen( } ) } + + // 출고 목록 composable(ROUTE_OUTBOUND) { OutboundListScreen( paddingValues = innerPadding ) } + + // 장바구니 composable(ROUTE_CART) { CartListScreen( paddingValues = innerPadding ) } + + // 주문 관리 composable(ROUTE_ORDERS) { OrderListScreen( paddingValues = innerPadding, @@ -283,6 +314,7 @@ fun MainScreen( } } +/** Floating Button */ @Composable fun PartsFab(navController: NavHostController) { FloatingActionButton( @@ -305,6 +337,7 @@ fun PartsFab(navController: NavHostController) { } } +/** Navigation Bar */ @Composable fun BottomNavigationBar(navController: NavHostController) { val bottomNavItems = listOf( diff --git a/app/src/main/java/com/sampoom/android/core/di/NetworkModule.kt b/app/src/main/java/com/sampoom/android/core/di/NetworkModule.kt index d1bf160..4d6660b 100644 --- a/app/src/main/java/com/sampoom/android/core/di/NetworkModule.kt +++ b/app/src/main/java/com/sampoom/android/core/di/NetworkModule.kt @@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder import com.sampoom.android.BuildConfig import com.sampoom.android.core.network.TokenAuthenticator import com.sampoom.android.core.network.TokenInterceptor +import com.sampoom.android.core.network.TokenLogoutEmitter import com.sampoom.android.core.network.TokenRefreshService import com.sampoom.android.core.preferences.AuthPreferences import dagger.Module @@ -24,9 +25,10 @@ object NetworkModule { @Provides @Singleton fun provideTokenInterceptor( - authPreferences: AuthPreferences + authPreferences: AuthPreferences, + tokenLogoutEmitter: TokenLogoutEmitter ): TokenInterceptor { - return TokenInterceptor(authPreferences) + return TokenInterceptor(authPreferences, tokenLogoutEmitter) } @Provides @@ -41,7 +43,7 @@ object NetworkModule { @Singleton fun provideOkHttpClient( tokenInterceptor: TokenInterceptor, - tokenAuthenticator: TokenAuthenticator +// tokenAuthenticator: TokenAuthenticator ): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) @@ -58,7 +60,7 @@ object NetworkModule { } ) .addInterceptor(tokenInterceptor) // 토큰 자동 삽입 - .authenticator(tokenAuthenticator) // 토큰 갱신 (Interceptor 대신) +// .authenticator(tokenAuthenticator) // 토큰 갱신 (Interceptor 대신) .build() } diff --git a/app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt b/app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt index 516ea2f..782381b 100644 --- a/app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt +++ b/app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt @@ -6,6 +6,7 @@ import com.google.gson.JsonSyntaxException import com.sampoom.android.core.model.ApiErrorResponse import retrofit2.HttpException +/** 서버에서 반환되는 에러 메시지를 추출하는 함수 */ fun Throwable.serverMessageOrNull(): String? { if (this is HttpException) { val errorBody = response()?.errorBody()?.string() ?: return null diff --git a/app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt b/app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt index 731fe0a..5d71989 100644 --- a/app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt +++ b/app/src/main/java/com/sampoom/android/core/network/TokenAuthenticator.kt @@ -11,10 +11,12 @@ import okhttp3.Route import javax.inject.Inject import javax.inject.Singleton +/** 토큰 Authentication 인증 로직 */ @Singleton class TokenAuthenticator @Inject constructor( private val authPreferences: AuthPreferences, - private val tokenRefreshService: TokenRefreshService + private val tokenRefreshService: TokenRefreshService, + private val tokenLogoutEmitter: TokenLogoutEmitter ) : Authenticator { private val refreshMutex = Mutex() @@ -41,7 +43,10 @@ class TokenAuthenticator @Inject constructor( when (e.code()) { 400, 401 -> { // 인증 실패: 토큰 삭제 - runBlocking { authPreferences.clear() } + runBlocking { + authPreferences.clear() + tokenLogoutEmitter.emit() + } null } 403 -> { diff --git a/app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt b/app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt index 4e79a97..5fe9a66 100644 --- a/app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt +++ b/app/src/main/java/com/sampoom/android/core/network/TokenInterceptor.kt @@ -3,11 +3,14 @@ package com.sampoom.android.core.network import com.sampoom.android.core.preferences.AuthPreferences import kotlinx.coroutines.runBlocking import okhttp3.Interceptor +import okhttp3.Request import okhttp3.Response import javax.inject.Inject +/** 토큰 Interceptor */ class TokenInterceptor @Inject constructor( - private val authPreferences: AuthPreferences + private val authPreferences: AuthPreferences, + private val tokenLogoutEmitter: TokenLogoutEmitter ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() @@ -17,7 +20,7 @@ class TokenInterceptor @Inject constructor( val requestWithoutFlag = originalRequest.newBuilder() .removeHeader("X-No-Auth") .build() - return chain.proceed(requestWithoutFlag) + return proceedAndLogoutOnForbidden(chain, requestWithoutFlag) } val existingAuth = originalRequest.header("Authorization") @@ -29,10 +32,21 @@ class TokenInterceptor @Inject constructor( val newRequest = originalRequest.newBuilder() .header("Authorization", "Bearer $accessToken") .build() - return chain.proceed(newRequest) + return proceedAndLogoutOnForbidden(chain, newRequest) } } - return chain.proceed(originalRequest) + return proceedAndLogoutOnForbidden(chain, originalRequest) + } + + private fun proceedAndLogoutOnForbidden(chain: Interceptor.Chain, request: Request): Response { + val response = chain.proceed(request) + if (response.code == 401) { + runBlocking { + authPreferences.clear() + tokenLogoutEmitter.emit() + } + } + return response } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/core/network/TokenLogoutEmitter.kt b/app/src/main/java/com/sampoom/android/core/network/TokenLogoutEmitter.kt new file mode 100644 index 0000000..8c4edf3 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/core/network/TokenLogoutEmitter.kt @@ -0,0 +1,17 @@ +package com.sampoom.android.core.network + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TokenLogoutEmitter @Inject constructor() { + private val _events = MutableSharedFlow(extraBufferCapacity = 1) + val events: SharedFlow = _events.asSharedFlow() + + suspend fun emit() { + _events.emit(Unit) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt b/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt index 1cf693d..a8d107e 100644 --- a/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt +++ b/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt @@ -9,6 +9,7 @@ import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Inject import javax.inject.Singleton +/** 토큰 Refresh 로직 */ @Singleton class TokenRefreshService @Inject constructor( private val authPreferences: AuthPreferences diff --git a/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt b/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt index e6787fe..a132118 100644 --- a/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt +++ b/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt @@ -16,6 +16,7 @@ import javax.inject.Singleton // Per official guidance, DataStore instance should be single and at top-level. private val Context.authDataStore by preferencesDataStore(name = "auth_prefs") +/** Encrypt Shared Preferences with CryptoManager */ @Singleton class AuthPreferences @Inject constructor( @param:ApplicationContext private val context: Context, @@ -39,6 +40,7 @@ class AuthPreferences @Inject constructor( val USER_ENDED_AT: Preferences.Key = stringPreferencesKey("user_ended_at") } + /** User 모델 저장 */ suspend fun saveUser(user: User) { val expiresAt = System.currentTimeMillis() + (user.expiresIn * 1000) dataStore.edit { prefs -> @@ -58,6 +60,7 @@ class AuthPreferences @Inject constructor( } } + /** User 토큰 저장 */ suspend fun saveToken(accessToken: String, refreshToken: String, expiresIn: Long) { val expiresAt = System.currentTimeMillis() + (expiresIn * 1000) dataStore.edit { prefs -> @@ -67,6 +70,7 @@ class AuthPreferences @Inject constructor( } } + /** User 모델 조회 */ suspend fun getStoredUser(): User? { val prefs = dataStore.data.first() val userId = prefs[Keys.USER_ID] @@ -114,6 +118,7 @@ class AuthPreferences @Inject constructor( } else return null } + /** Access Token 조회 */ suspend fun getAccessToken(): String? { val encrypted = dataStore.data.first()[Keys.ACCESS_TOKEN] ?: return null return try { @@ -123,6 +128,7 @@ class AuthPreferences @Inject constructor( } } + /** RefreshToken 조회 */ suspend fun getRefreshToken(): String? { val encrypted = dataStore.data.first()[Keys.REFRESH_TOKEN] ?: return null return try { @@ -132,15 +138,18 @@ class AuthPreferences @Inject constructor( } } + /** Token 만료 조회 */ suspend fun isTokenExpired(): Boolean { val expiresAt = dataStore.data.first()[Keys.TOKEN_EXPIRES_AT] return expiresAt == null || System.currentTimeMillis() > expiresAt } + /** 저장된 User 모델 삭제 */ suspend fun clear() { dataStore.edit { it.clear() } } + /** 토큰 여부 판별 */ suspend fun hasToken(): Boolean { val accessToken = getAccessToken() val refreshToken = getRefreshToken() diff --git a/app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt b/app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt index 976a1aa..7fc48db 100644 --- a/app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt +++ b/app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt @@ -43,6 +43,7 @@ import com.sampoom.android.core.ui.theme.textSecondaryColor * onClick = { ... } * ) */ + @Composable fun CommonButton( modifier: Modifier = Modifier, 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 d20d25d..875e25a 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 @@ -15,20 +15,26 @@ import retrofit2.http.Headers import retrofit2.http.POST interface AuthApi { + // 회원가입 @POST("auth/signup") @Headers("X-No-Auth: true") suspend fun signUp(@Body body: SignUpRequestDto): ApiResponse + // 토큰 갱신 @POST("auth/refresh") + @Headers("X-No-Auth: true") suspend fun refresh(@Body body: RefreshRequestDto): ApiResponse + // 로그아웃 @POST("auth/logout") suspend fun logout(): ApiSuccessResponse + // 로그인 @POST("auth/login") @Headers("X-No-Auth: true") suspend fun login(@Body body: LoginRequestDto): ApiResponse + // 대리점 조회 @GET("site/vendors") suspend fun getVendors(): ApiResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt index f1e5106..0c67b82 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt @@ -18,6 +18,7 @@ class AuthRepositoryImpl @Inject constructor( private val preferences: AuthPreferences ) : AuthRepository { + /** 회원가입 */ override suspend fun signUp( email: String, password: String, @@ -46,6 +47,7 @@ class AuthRepositoryImpl @Inject constructor( } } + /** 로그인 */ override suspend fun signIn( email: String, password: String @@ -66,6 +68,7 @@ class AuthRepositoryImpl @Inject constructor( } } + /** 로그아웃 */ override suspend fun signOut(): Result { return runCatching { val dto = api.logout() @@ -77,6 +80,7 @@ class AuthRepositoryImpl @Inject constructor( } } + /** 토큰 갱신 */ override suspend fun refreshToken(): Result { return runCatching { val refreshToken = @@ -95,14 +99,17 @@ class AuthRepositoryImpl @Inject constructor( } } + /** 토큰 제거 */ override suspend fun clearTokens(): Result { return runCatching { preferences.clear() } } + /** 로그인 여부 */ override suspend fun isSignedIn(): Boolean = preferences.hasToken() + /** 대리점 조회 */ override suspend fun getVendorList(): Result { return runCatching { val dto = api.getVendors() diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/AuthViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/AuthViewModel.kt index efb1e6a..9e47944 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/AuthViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/AuthViewModel.kt @@ -2,6 +2,7 @@ package com.sampoom.android.feature.auth.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.sampoom.android.core.network.TokenLogoutEmitter import com.sampoom.android.feature.auth.domain.usecase.CheckLoginStateUseCase import com.sampoom.android.feature.auth.domain.usecase.ClearTokensUseCase import com.sampoom.android.feature.auth.domain.usecase.SignOutUseCase @@ -19,7 +20,8 @@ import javax.inject.Inject class AuthViewModel @Inject constructor( private val checkLoginStateUseCase: CheckLoginStateUseCase, private val signOutUseCase: SignOutUseCase, - private val clearTokensUseCase: ClearTokensUseCase + private val clearTokensUseCase: ClearTokensUseCase, + tokenLogoutEmitter: TokenLogoutEmitter ) : ViewModel() { private val _isLoggedIn = MutableStateFlow(false) val isLoggedIn: StateFlow = _isLoggedIn.asStateFlow() @@ -37,6 +39,9 @@ class AuthViewModel @Inject constructor( viewModelScope.launch { _isLoggedIn.value = checkLoginStateUseCase() _isLoading.value = false + tokenLogoutEmitter.events.collect { + signOut() + } } } 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 4be3eb4..df32dd5 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 @@ -47,13 +47,12 @@ fun LoginScreen( val emailLabel = stringResource(R.string.login_title_email) val passwordLabel = stringResource(R.string.login_title_password) val errorLabel = stringResource(R.string.common_error) + val uiState by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffect(emailLabel, passwordLabel, errorLabel) { viewModel.bindLabel(emailLabel, passwordLabel, errorLabel) } - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - LaunchedEffect(uiState.success) { if (uiState.success) onSuccess() } 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 dcb9ef7..933ca91 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 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val messageHandler: GlobalMessageHandler, - private val singIn: LoginUseCase, - private val getProfile: GetProfileUseCase, + private val loginUseCase: LoginUseCase, + private val getProfileUseCase: GetProfileUseCase, private val application: Application ) : ViewModel() { @@ -86,9 +86,9 @@ class LoginViewModel @Inject constructor( val s = _uiState.value _uiState.update { it.copy(loading = true, success = false) } - singIn(s.email, s.password) + loginUseCase(s.email, s.password) .onSuccess { - getProfile("AGENCY") + getProfileUseCase("AGENCY") .onSuccess { _uiState.update { it.copy(loading = false, success = true) 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 d7fe3a3..feff6da 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,5 +1,6 @@ package com.sampoom.android.feature.auth.ui +import android.R.attr.labelTextSize import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -67,22 +68,20 @@ fun SignUpScreen( val emailFocus = remember { FocusRequester() } val passwordFocus = remember { FocusRequester() } val passwordCheckFocus = remember { FocusRequester() } + val positionItems = remember { UserPosition.entries } + var positionMenuExpanded by remember { mutableStateOf(false) } + var vendorMenuExpanded by remember { mutableStateOf(false) } + + val state by viewModel.state.collectAsState() LaunchedEffect(nameLabel, branchLabel, positionLabel, errorLabel) { viewModel.bindLabels(nameLabel, branchLabel, positionLabel, errorLabel) } - val state by viewModel.state.collectAsState() - val labelTextSize = 16.sp - LaunchedEffect(state.success) { if (state.success) onSuccess() } - val positionItems = remember { UserPosition.entries } - var positionMenuExpanded by remember { mutableStateOf(false) } - var vendorMenuExpanded by remember { mutableStateOf(false) } - Scaffold( topBar = { TopAppBar( @@ -97,8 +96,7 @@ fun SignUpScreen( } ) }, - contentWindowInsets = ScaffoldDefaults.contentWindowInsets, -// snackbarHost = { CommonSnackBarHost(snackBarHostState) } + contentWindowInsets = ScaffoldDefaults.contentWindowInsets ) { innerPadding -> val focusManager = LocalFocusManager.current Box( // 터치 감지용 컨테이너 @@ -127,7 +125,7 @@ fun SignUpScreen( Spacer(Modifier.height(48.dp)) Text( text = stringResource(R.string.signup_title_name), - fontSize = labelTextSize + fontSize = 16.sp ) CommonTextField( modifier = Modifier.fillMaxWidth().focusRequester(nameFocus), @@ -142,7 +140,7 @@ fun SignUpScreen( Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_branch), - fontSize = labelTextSize + fontSize = 16.sp ) ExposedDropdownMenuBox( expanded = vendorMenuExpanded, @@ -191,7 +189,7 @@ fun SignUpScreen( Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_position), - fontSize = labelTextSize + fontSize = 16.sp ) ExposedDropdownMenuBox( expanded = positionMenuExpanded, @@ -231,7 +229,7 @@ fun SignUpScreen( Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_email), - fontSize = labelTextSize + fontSize = 16.sp ) CommonTextField( modifier = Modifier.fillMaxWidth().focusRequester(emailFocus), @@ -246,7 +244,7 @@ fun SignUpScreen( Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_password), - fontSize = labelTextSize + fontSize = 16.sp ) CommonTextField( modifier = Modifier.fillMaxWidth().focusRequester(passwordFocus), @@ -262,7 +260,7 @@ fun SignUpScreen( Spacer(Modifier.height(8.dp)) Text( text = stringResource(R.string.signup_title_password_check), - fontSize = labelTextSize + fontSize = 16.sp ) CommonTextField( modifier = Modifier.fillMaxWidth().focusRequester(passwordCheckFocus) , diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt index 90bfe1d..2e85f3d 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt @@ -5,7 +5,6 @@ import com.sampoom.android.feature.auth.domain.model.Vendor sealed interface SignUpUiEvent { data class NameChanged(val name: String) : SignUpUiEvent -// data class BranchChanged(val branch: String) : SignUpUiEvent data class VendorChanged(val vendor: Vendor) : SignUpUiEvent data class PositionChanged(val position: UserPosition) : SignUpUiEvent data class EmailChanged(val email: String) : SignUpUiEvent diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt index 8430b26..1376f8d 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt @@ -59,10 +59,6 @@ class SignUpViewModel @Inject constructor( _state.value = _state.value.copy(name = e.name) validateName() } -// is SignUpUiEvent.BranchChanged -> { -// _state.value = _state.value.copy(branch = e.branch) -// validateBranch() -// } is SignUpUiEvent.VendorChanged -> { _state.value = _state.value.copy( selectedVendor = e.vendor, @@ -83,9 +79,7 @@ class SignUpViewModel @Inject constructor( validatePassword() if (_state.value.passwordCheck.isNotBlank()) { validatePasswordCheck() - } else { - - } + } else { } } is SignUpUiEvent.PasswordCheckChanged -> { _state.value = _state.value.copy(passwordCheck = e.passwordCheck) diff --git a/app/src/main/java/com/sampoom/android/feature/cart/data/repository/CartRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/cart/data/repository/CartRepositoryImpl.kt index a8554d4..a113821 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/data/repository/CartRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/data/repository/CartRepositoryImpl.kt @@ -13,6 +13,7 @@ class CartRepositoryImpl @Inject constructor( private val api: CartApi, private val authPreferences: AuthPreferences ) : CartRepository { + /** 장바구니 리스트 조회 */ override suspend fun getCartList(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -22,6 +23,7 @@ class CartRepositoryImpl @Inject constructor( } } + /** 장바구니 추가 */ override suspend fun addCart( partId: Long, quantity: Long @@ -33,6 +35,7 @@ class CartRepositoryImpl @Inject constructor( } } + /** 장바구니 삭제 */ override suspend fun deleteCart(cartItemId: Long): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -41,6 +44,7 @@ class CartRepositoryImpl @Inject constructor( } } + /** 장바구니 전체 삭제 */ override suspend fun deleteAllCart(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -49,6 +53,7 @@ class CartRepositoryImpl @Inject constructor( } } + /** 장바구니 수량 수정 */ override suspend fun updateCartQuantity( cartItemId: Long, quantity: Long diff --git a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt index d17be0e..df67523 100644 --- a/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/cart/ui/CartListScreen.kt @@ -252,6 +252,7 @@ fun CartListScreen( } } +/** 장바구니 카테고리 > 그룹 별 섹션 */ @Composable private fun CartSection( categoryName: String, @@ -281,6 +282,7 @@ private fun CartSection( } } +/** 장바구니 부품 아이템 */ @Composable private fun CartPartItem( part: CartPart, diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/data/repository/DashboardRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/data/repository/DashboardRepositoryImpl.kt index 5f0f1d9..6e2d225 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/data/repository/DashboardRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/data/repository/DashboardRepositoryImpl.kt @@ -12,6 +12,7 @@ class DashboardRepositoryImpl @Inject constructor( private val api: DashboardApi, private val authPreferences: AuthPreferences ) : DashboardRepository { + /** 대시보드 조회 */ override suspend fun getDashboard(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -20,6 +21,7 @@ class DashboardRepositoryImpl @Inject constructor( } } + /** 이번 주 요약 조회 */ override suspend fun getWeeklySummary(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt index 9245950..5609167 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt @@ -47,6 +47,7 @@ import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.sampoom.android.R +import com.sampoom.android.core.model.UserPosition import com.sampoom.android.core.ui.component.EmptyContent import com.sampoom.android.core.ui.component.ErrorContent import com.sampoom.android.core.ui.theme.FailRed @@ -75,7 +76,15 @@ fun DashboardScreen( val user by viewModel.user.collectAsStateWithLifecycle() val pullRefreshState = rememberPullToRefreshState() val orderListPaged = viewModel.orderListPaged.collectAsLazyPagingItems() - val isManager = user?.role == "ADMIN" + val isManager = user?.position in setOf( + UserPosition.MANAGER, + UserPosition.DEPUTY_GENERAL_MANAGER, + UserPosition.GENERAL_MANAGER, + UserPosition.DIRECTOR, + UserPosition.VICE_PRESIDENT, + UserPosition.PRESIDENT, + UserPosition.CHAIRMAN + ) LaunchedEffect(errorLabel) { viewModel.bindLabel(errorLabel) @@ -184,6 +193,7 @@ fun DashboardScreen( } } +/** 대시보드 타이들 섹션 */ @Composable fun TitleSection( user: User? @@ -227,6 +237,7 @@ fun TitleSection( } } +/** 부품 통계 섹션 */ @Composable fun ButtonSection( onEmployeeClick: () -> Unit, @@ -302,6 +313,7 @@ fun ButtonSection( } } +/** 통계 카드 */ @Composable fun ButtonCard( modifier: Modifier, @@ -353,6 +365,7 @@ fun ButtonCard( } } +/** 주문 내역 섹션 */ @Composable fun OrderListSection( orderListPaged: LazyPagingItems, @@ -447,6 +460,7 @@ fun OrderListSection( } } +/** 이번 주 요약 섹션 */ @Composable fun WeeklySummarySection( modifier: Modifier, diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt index f6d3f82..54a2b15 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt @@ -184,6 +184,7 @@ fun SettingScreen( } } +/** 프로필 조회 섹션 */ @Composable fun UserSection( user: User?, @@ -236,6 +237,7 @@ fun UserSection( } } +/** 설정 항목 섹션 */ @Composable private fun SettingSection( onEditProfileClick: () -> Unit, diff --git a/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt index 8190a7c..e0c7545 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/data/repository/OrderRepositoryImpl.kt @@ -24,6 +24,7 @@ class OrderRepositoryImpl @Inject constructor( private val authPreferences: AuthPreferences, private val pagingSourceFactory: OrderPagingSource.Factory ) : OrderRepository { + /** 주문 내역 리스트 조회 */ override fun getOrderList(): Flow> { return Pager( config = PagingConfig(pageSize = 20), @@ -31,6 +32,7 @@ class OrderRepositoryImpl @Inject constructor( ).flow } + /** 주문 처리 */ override suspend fun createOrder(cartList: CartList): Result { return runCatching { val agencyName = authPreferences.getStoredUser()?.branch ?: throw Exception() @@ -67,6 +69,7 @@ class OrderRepositoryImpl @Inject constructor( } } + /** 주문 상태 완료 처리 */ override suspend fun completeOrder(orderId: Long): Result { return runCatching { val dto = api.completeOrder(orderId) @@ -74,6 +77,7 @@ class OrderRepositoryImpl @Inject constructor( } } + /** 주문 내역 입고 처리 */ override suspend fun receiveOrder(items: List>): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -87,6 +91,7 @@ class OrderRepositoryImpl @Inject constructor( } } + /** 주문 내역 상세 조회 */ override suspend fun getOrderDetail(orderId: Long): Result { return runCatching { val dto = api.getOrderDetail(orderId) @@ -95,6 +100,7 @@ class OrderRepositoryImpl @Inject constructor( } } + /** 주문 취소 처리 */ override suspend fun cancelOrder(orderId: Long): Result { return runCatching { val dto = api.cancelOrder(orderId) diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt index 5141d9f..e67a8f8 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderDetailContent.kt @@ -33,6 +33,7 @@ import com.sampoom.android.feature.order.domain.model.OrderPart import com.sampoom.android.feature.order.domain.model.subtotal import com.sampoom.android.feature.order.domain.model.totalCost +/** 주문 상세 내역 화면 */ @Composable fun OrderDetailContent( order: Order, @@ -70,7 +71,7 @@ fun OrderDetailContent( } } - +/** 주문 정보 섹션 */ @Composable private fun OrderInfoCard(order: Order) { Card( @@ -127,6 +128,7 @@ private fun OrderInfoCard(order: Order) { } } +/** 주문 내역 섹션 */ @Composable private fun OrderInfoRow( label: String, @@ -172,6 +174,7 @@ private fun OrderSection( } } +/** 부품 내역 섹션 */ @Composable private fun OrderPartItem( part: OrderPart @@ -219,7 +222,7 @@ private fun OrderPartItem( } Spacer(Modifier.padding(4.dp)) - Divider(Modifier.background(textSecondaryColor())) + Divider(Modifier.background(textSecondaryColor()).height(0.5.dp)) Spacer(Modifier.padding(4.dp)) Text( diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt index e7776d4..fa87cde 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderItem.kt @@ -27,6 +27,7 @@ import com.sampoom.android.core.util.formatWon import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.model.totalCost +/** 주문 아이템 섹션 */ @Composable fun OrderItem( order: Order, diff --git a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderResultBottomSheet.kt b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderResultBottomSheet.kt index 0f3a050..9b2ecc0 100644 --- a/app/src/main/java/com/sampoom/android/feature/order/ui/OrderResultBottomSheet.kt +++ b/app/src/main/java/com/sampoom/android/feature/order/ui/OrderResultBottomSheet.kt @@ -42,6 +42,7 @@ import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.model.OrderStatus +/** 주문 완료 Bottom Sheet */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun OrderResultBottomSheet( @@ -141,6 +142,7 @@ fun OrderResultBottomSheet( } } +/** 주문 완료 Bottom Sheet Header */ @Composable private fun OrderCompleteHeader() { Row( diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/data/repository/OutboundRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/outbound/data/repository/OutboundRepositoryImpl.kt index bb3fe7b..f2e5e1c 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/data/repository/OutboundRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/data/repository/OutboundRepositoryImpl.kt @@ -13,6 +13,7 @@ class OutboundRepositoryImpl @Inject constructor( private val api: OutboundApi, private val authPreferences: AuthPreferences ) : OutboundRepository { + /** 출고 처리 내역 리스트 조회 */ override suspend fun getOutboundList(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -22,6 +23,7 @@ class OutboundRepositoryImpl @Inject constructor( } } + /** 출고 처리 */ override suspend fun processOutbound(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -30,6 +32,7 @@ class OutboundRepositoryImpl @Inject constructor( } } + /** 출고 목록 추가 */ override suspend fun addOutbound( partId: Long, quantity: Long @@ -42,6 +45,7 @@ class OutboundRepositoryImpl @Inject constructor( } } + /** 출고 목록 삭제 */ override suspend fun deleteOutbound(outboundId: Long): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -50,6 +54,7 @@ class OutboundRepositoryImpl @Inject constructor( } } + /** 출고 목록 전체 삭제 */ override suspend fun deleteAllOutbound(): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -58,6 +63,7 @@ class OutboundRepositoryImpl @Inject constructor( } } + /** 출고 목록 수량 변경 */ override suspend fun updateOutboundQuantity( outboundId: Long, quantity: Long diff --git a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt index a36e706..7a69959 100644 --- a/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/outbound/ui/OutboundListScreen.kt @@ -256,6 +256,7 @@ fun OutboundListScreen( } } +/** 출고 목록 카테고리 > 그룹 섹션 */ @Composable private fun OutboundSection( categoryName: String, @@ -285,6 +286,7 @@ private fun OutboundSection( } } +/** 출고 목록 부품 섹션 */ @Composable private fun OutboundPartItem( part: OutboundPart, 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 ac7c808..a609591 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 @@ -10,18 +10,22 @@ import retrofit2.http.Path import retrofit2.http.Query interface PartApi { + // 카테고리 조회 @GET("agency/category") suspend fun getCategoryList(): ApiResponse> + // 그룹 조회 @GET("agency/category/{categoryId}") suspend fun getGroupList(@Path("categoryId") categoryId: Long): ApiResponse> + // 부품 리스트 조회 @GET("agency/{agencyId}/group/{groupId}") suspend fun getPartList( @Path("agencyId") agencyId: Long, @Path("groupId") groupId: Long ): ApiResponse> + // 부품 검색 @GET("agency/{agencyId}/search") suspend fun searchParts( @Path("agencyId") agencyId: 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 c9b45e1..0ba6c36 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 @@ -20,6 +20,7 @@ class PartRepositoryImpl @Inject constructor( private val authPreferences: AuthPreferences, private val pagingSourceFactory: PartPagingSource.Factory ) : PartRepository { + /** 카테고리 조회 */ override suspend fun getCategoryList(): Result { return runCatching { val dto = api.getCategoryList() @@ -28,6 +29,7 @@ class PartRepositoryImpl @Inject constructor( } } + /** 그룹 조회 */ override suspend fun getGroupList(categoryId: Long): Result { return runCatching { val response = api.getGroupList(categoryId) @@ -36,6 +38,7 @@ class PartRepositoryImpl @Inject constructor( } } + /** 부품 조회 */ override suspend fun getPartList(groupId: Long): Result { return runCatching { val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() @@ -45,6 +48,7 @@ class PartRepositoryImpl @Inject constructor( } } + /** 부품 검색 */ override fun searchParts(keyword: String): Flow> { return Pager( config = PagingConfig(pageSize = 20), 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 index 919e173..2e2b1fa 100644 --- 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 @@ -207,6 +207,7 @@ fun PartListScreen( } } +/** 부품 리스트 아이템 섹션 */ @Composable private fun PartListItemCard( part: Part, 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 48fcd67..96b940c 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 @@ -444,7 +444,7 @@ fun PartScreen( } } -// Category 아이템 +/** Category 아이템 */ @Composable fun CategoryItem( category: Category, @@ -481,6 +481,7 @@ fun CategoryItem( } } +/** 카테고리 이미지 Mapper */ private fun resourceMapper(code: String): Int { return when (code) { "ENG" -> R.drawable.engine @@ -493,6 +494,7 @@ private fun resourceMapper(code: String): Int { } } +/** 부품 아이템 섹션 */ @Composable private fun PartItemCard( group: Group, @@ -525,6 +527,7 @@ private fun PartItemCard( } } +/** 검색 결과 리스트 섹션 */ @Composable fun SearchResultsList( searchResults: LazyPagingItems, @@ -585,6 +588,7 @@ fun SearchResultsList( } } +/** 부품 검색 아이템 섹샨 */ @Composable private fun SearchPartItem( part: Part, diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt index a885502..be26fdc 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt @@ -16,12 +16,15 @@ import retrofit2.http.Path import retrofit2.http.Query interface UserApi { + // 프로필 조회 @GET("user/profile") suspend fun getProfile(@Query("workspace") workspace: String): ApiResponse + // 프로필 수정 @PATCH("user/profile") suspend fun updateProfile(@Body body: UpdateProfileRequestDto): ApiResponse + // 직원 프로필 수정 @PATCH("user/profile/{userId}") suspend fun editEmployee( @Path("userId") userId: Long, @@ -29,6 +32,7 @@ interface UserApi { @Body body: EditEmployeeRequestDto ): ApiResponse + // 직원 목록 조회 @GET("user/info") suspend fun getEmployeeList( @Query("workspace") workspace: String, @@ -37,6 +41,7 @@ interface UserApi { @Query("size") size: Int = 20 ): ApiResponse + // 직원 상태 수정 @PATCH("user/status/{userId}") suspend fun updateEmployeeStatus( @Path("userId") userId: Long, diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt index 25469c2..79ca8ee 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt @@ -22,10 +22,12 @@ class UserRepositoryImpl @Inject constructor( private val preferences: AuthPreferences, private val pagingSourceFactory: EmployeePagingSource.Factory ) : UserRepository { + /** 유저 프로필 조회 */ override suspend fun getStoredUser(): User? { return preferences.getStoredUser() } + /** 프로필 조회 */ override suspend fun getProfile(workspace: String): Result { return runCatching { retry(times = 5, initialDelay = 300) { @@ -60,6 +62,7 @@ class UserRepositoryImpl @Inject constructor( } } + /** 프로필 수정 */ override suspend fun updateProfile(user: User): Result { return runCatching { val requestDto = UpdateProfileRequestDto( @@ -92,6 +95,7 @@ class UserRepositoryImpl @Inject constructor( } } + /** 직원 목록 조회 */ override fun getEmployeeList(): Flow> { return Pager( config = PagingConfig(pageSize = 20), @@ -99,6 +103,7 @@ class UserRepositoryImpl @Inject constructor( ).flow } + /** 직원 프로필 수정 */ override suspend fun editEmployee( employee: Employee, workspace: String @@ -135,6 +140,7 @@ class UserRepositoryImpl @Inject constructor( } } + /** 직원 상태 수정 */ override suspend fun updateEmployeeStatus( employee: Employee, workspace: String @@ -171,6 +177,7 @@ class UserRepositoryImpl @Inject constructor( } } + /** 직원 수 조회 */ override suspend fun getEmployeeCount(): Result { return runCatching { val user = preferences.getStoredUser() ?: throw Exception() diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt index cab05cf..bf43dfa 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt @@ -65,12 +65,10 @@ fun EmployeeListScreen( onNavigateBack: () -> Unit = {}, viewModel: EmployeeListViewModel = hiltViewModel() ) { - val coroutineScope = rememberCoroutineScope() val errorLabel = stringResource(R.string.common_error) val uiState by viewModel.uiState.collectAsStateWithLifecycle() val employeeListPaged = viewModel.employeeListPaged.collectAsLazyPagingItems() val pullRefreshState = rememberPullToRefreshState() - val listState = rememberSaveable(saver = LazyListState.Saver) { LazyListState() } val sheetState = rememberModalBottomSheetState(true) val selectedEmployee = uiState.selectedEmployee val bottomSheetType = uiState.bottomSheetType