diff --git a/data/src/main/java/com/nexters/boolti/data/datasource/UserDataSource.kt b/data/src/main/java/com/nexters/boolti/data/datasource/UserDataSource.kt index 4073dcd9..38df5dd5 100644 --- a/data/src/main/java/com/nexters/boolti/data/datasource/UserDataSource.kt +++ b/data/src/main/java/com/nexters/boolti/data/datasource/UserDataSource.kt @@ -8,6 +8,10 @@ import javax.inject.Inject internal class UserDataSource @Inject constructor( private val userService: UserService, ) { - suspend fun getUser(): UserResponse = userService.getUser() + suspend fun getUser(): UserResponse? { + val response = userService.getUser() + return if (response.isSuccessful) response.body() else null + } + suspend fun signout(request: SignoutRequest) = userService.signout(request) } diff --git a/data/src/main/java/com/nexters/boolti/data/network/api/UserService.kt b/data/src/main/java/com/nexters/boolti/data/network/api/UserService.kt index 8cfc7032..018b562d 100644 --- a/data/src/main/java/com/nexters/boolti/data/network/api/UserService.kt +++ b/data/src/main/java/com/nexters/boolti/data/network/api/UserService.kt @@ -2,13 +2,14 @@ package com.nexters.boolti.data.network.api import com.nexters.boolti.data.network.response.UserResponse import com.nexters.boolti.domain.request.SignoutRequest +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.HTTP internal interface UserService { @GET("/app/api/v1/user") - suspend fun getUser(): UserResponse + suspend fun getUser(): Response @HTTP(method = "DELETE", path = "/app/api/v1/user", hasBody = true) suspend fun signout( diff --git a/data/src/main/java/com/nexters/boolti/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/nexters/boolti/data/repository/AuthRepositoryImpl.kt index 530767db..f1273a2b 100644 --- a/data/src/main/java/com/nexters/boolti/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/nexters/boolti/data/repository/AuthRepositoryImpl.kt @@ -54,10 +54,13 @@ internal class AuthRepositoryImpl @Inject constructor( authDataSource.localLogout() } - override fun getUserAndCache(): Flow = flow { + override fun getUserAndCache(): Flow = flow { val response = userDateSource.getUser() - authDataSource.updateUser(response) - emit(response.toDomain()) + response?.let { + authDataSource.updateUser(it) + } + + emit(response?.toDomain()) } override suspend fun sendFcmToken(): Result = deviceTokenDataSource.sendFcmToken() diff --git a/domain/src/main/java/com/nexters/boolti/domain/repository/AuthRepository.kt b/domain/src/main/java/com/nexters/boolti/domain/repository/AuthRepository.kt index cd827031..a65d0731 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/repository/AuthRepository.kt @@ -20,7 +20,7 @@ interface AuthRepository { suspend fun logout(): Result suspend fun signUp(signUpRequest: SignUpRequest): Result suspend fun signout(request: SignoutRequest): Result - fun getUserAndCache(): Flow + fun getUserAndCache(): Flow suspend fun sendFcmToken(): Result val loggedIn: Flow diff --git a/domain/src/main/java/com/nexters/boolti/domain/usecase/GetUserUsecase.kt b/domain/src/main/java/com/nexters/boolti/domain/usecase/GetUserUsecase.kt index 768ea186..fe41e444 100644 --- a/domain/src/main/java/com/nexters/boolti/domain/usecase/GetUserUsecase.kt +++ b/domain/src/main/java/com/nexters/boolti/domain/usecase/GetUserUsecase.kt @@ -9,5 +9,5 @@ import javax.inject.Inject class GetUserUsecase @Inject constructor( private val authRepository: AuthRepository, ) { - operator fun invoke(): User = runBlocking { authRepository.getUserAndCache().first() } + operator fun invoke(): User? = runBlocking { authRepository.getUserAndCache().first() } } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftViewModel.kt index 700273f3..a88cf00c 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/gift/GiftViewModel.kt @@ -35,7 +35,9 @@ class GiftViewModel @Inject constructor( private val giftRepository: GiftRepository, private val getRefundPolicyUseCase: GetRefundPolicyUsecase, ) : BaseViewModel() { - private val userId = getUserUseCase().id + private val userId = checkNotNull(getUserUseCase()?.id) { + "[GiftViewModel] 사용자 정보가 없습니다." + } val showId: String = requireNotNull(savedStateHandle["showId"]) val salesTicketTypeId: String = requireNotNull(savedStateHandle["salesTicketId"]) private val ticketCount: Int = savedStateHandle["ticketCount"] ?: 1 diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeScreen.kt index 824ddc97..6ce58632 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeScreen.kt @@ -132,7 +132,6 @@ fun HomeScreen( ShowScreen( modifier = modifier.padding(innerPadding), onClickShowItem = onClickShowItem, - navigateToReservations = navigateToReservations, navigateToBusiness = navigateToBusiness, ) } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeViewModel.kt index 6f173c36..b0f4daab 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/home/HomeViewModel.kt @@ -38,12 +38,12 @@ class HomeViewModel @Inject constructor( private var pendingGiftUuid: String? = null init { - initUserInfo() + fetchUserInfo() sendFcmToken() collectDeepLinkEvent() } - private fun initUserInfo() { + private fun fetchUserInfo() { authRepository.getUserAndCache() .launchIn(viewModelScope + recordExceptionHandler) } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginEvent.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginEvent.kt index 32786b1e..4e269a99 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginEvent.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginEvent.kt @@ -5,4 +5,5 @@ sealed interface LoginEvent { data object RequireSignUp : LoginEvent data object SignOutCancelled : LoginEvent data object Invalid : LoginEvent + data object SignupFailed : LoginEvent } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginScreen.kt index b3de9caa..77645681 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginScreen.kt @@ -39,6 +39,7 @@ import com.nexters.boolti.presentation.component.BTDialog import com.nexters.boolti.presentation.component.BtCloseableAppBar import com.nexters.boolti.presentation.component.KakaoLoginButton import com.nexters.boolti.presentation.component.MainButton +import com.nexters.boolti.presentation.screen.LocalSnackbarController import com.nexters.boolti.presentation.theme.Grey30 import com.nexters.boolti.presentation.theme.marginHorizontal @@ -49,10 +50,13 @@ fun LoginScreen( modifier: Modifier = Modifier, viewModel: LoginViewModel = hiltViewModel(), ) { - val context = LocalContext.current + val snackbarController = LocalSnackbarController.current + val sheetState = rememberModalBottomSheetState() var showSignOutCancelledDialog by remember { mutableStateOf(false) } var isSheetOpen by rememberSaveable { mutableStateOf(false) } + val loginFailedMessage = stringResource(id = R.string.login_failed) + val signupFailedMessage = stringResource(id = R.string.signup_failed) LaunchedEffect(Unit) { viewModel.event.collect { @@ -60,7 +64,8 @@ fun LoginScreen( LoginEvent.Success -> onBackPressed() LoginEvent.RequireSignUp -> isSheetOpen = true LoginEvent.SignOutCancelled -> showSignOutCancelledDialog = true - LoginEvent.Invalid -> Toast.makeText(context, "로그인 실패", Toast.LENGTH_SHORT).show() + LoginEvent.Invalid -> snackbarController.showMessage(loginFailedMessage) + LoginEvent.SignupFailed -> snackbarController.showMessage(signupFailedMessage) } } } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginViewModel.kt index da0ea68c..2ca42909 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/login/LoginViewModel.kt @@ -2,6 +2,7 @@ package com.nexters.boolti.presentation.screen.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.nexters.boolti.domain.repository.AuthRepository import com.nexters.boolti.domain.request.LoginRequest import com.nexters.boolti.domain.request.OauthType @@ -39,7 +40,7 @@ class LoginViewModel @Inject constructor( else -> event(LoginEvent.Success) } }.onFailure { - Timber.d("login failed: $it") + FirebaseCrashlytics.getInstance().setCustomKey("LOGIN", "FAILED") event(LoginEvent.Invalid) } } @@ -78,7 +79,8 @@ class LoginViewModel @Inject constructor( ).onSuccess { event(LoginEvent.Success) }.onFailure { - // TODO 예외 처리 + FirebaseCrashlytics.getInstance().setCustomKey("SIGNUP", "FAILED") + event(LoginEvent.SignupFailed) } } } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/my/MyViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/my/MyViewModel.kt index 83a22b8a..95ac7fd9 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/my/MyViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/my/MyViewModel.kt @@ -21,12 +21,6 @@ class MyViewModel @Inject constructor( null, ) - fun logout() { - viewModelScope.launch { - authRepository.logout() - } - } - fun fetchMyInfo() { authRepository.getUserAndCache() .launchIn(viewModelScope + recordExceptionHandler) diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowScreen.kt index 5660e6ce..d32efedc 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowScreen.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowScreen.kt @@ -67,7 +67,6 @@ import com.nexters.boolti.presentation.theme.point4 @Composable fun ShowScreen( - navigateToReservations: () -> Unit, navigateToBusiness: () -> Unit, onClickShowItem: (showId: String) -> Unit, modifier: Modifier = Modifier, @@ -78,7 +77,7 @@ fun ShowScreen( val uiState by viewModel.uiState.collectAsStateWithLifecycle() val lazyGridState = rememberLazyGridState() - val appbarHeight = if (uiState.hasPendingTicket) 196.dp + 52.dp else 196.dp + val appbarHeight = 196.dp val searchBarHeight = 80.dp val changeableAppBarHeightPx = (appbarHeight - searchBarHeight).toPx() var appbarOffsetHeightPx by rememberSaveable { mutableFloatStateOf(0f) } @@ -102,7 +101,6 @@ fun ShowScreen( } LaunchedEffect(Unit) { - viewModel.fetchReservationInfo() viewModel.events.collect { event -> when (event) { ShowEvent.Search -> appbarOffsetHeightPx = 0f @@ -152,8 +150,6 @@ fun ShowScreen( y = appbarOffsetHeightPx.coerceAtLeast(-changeableAppBarHeightPx).toInt(), ) }, - navigateToReservations = navigateToReservations, - hasPendingTicket = uiState.hasPendingTicket, nickname = nickname.ifBlank { stringResource(id = R.string.nickname_default) }, text = uiState.keyword, onKeywordChanged = viewModel::updateKeyword, @@ -166,8 +162,6 @@ fun ShowScreen( @Composable fun ShowAppBar( text: String, - hasPendingTicket: Boolean, - navigateToReservations: () -> Unit, nickname: String, onKeywordChanged: (keyword: String) -> Unit, search: () -> Unit, @@ -180,10 +174,6 @@ fun ShowAppBar( .padding(horizontal = marginHorizontal) ) { Spacer(modifier = Modifier.height(20.dp)) - if (hasPendingTicket) Banner( - modifier = Modifier.fillMaxWidth(), - navigateToReservations = navigateToReservations, - ) Spacer(modifier = Modifier.height(20.dp)) Text( modifier = Modifier diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowUiState.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowUiState.kt index 1264ef21..baf5e8f9 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowUiState.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowUiState.kt @@ -5,5 +5,4 @@ import com.nexters.boolti.domain.model.Show data class ShowUiState( val keyword: String = "", val shows: List = emptyList(), - val hasPendingTicket: Boolean = false, ) \ No newline at end of file diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowViewModel.kt index 46dafb73..db732a8f 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/show/ShowViewModel.kt @@ -28,7 +28,6 @@ import javax.inject.Inject @HiltViewModel class ShowViewModel @Inject constructor( private val showRepository: ShowRepository, - private val reservationRepository: ReservationRepository, authRepository: AuthRepository, ) : BaseViewModel() { val user: StateFlow = authRepository.cachedUser.stateIn( @@ -67,17 +66,4 @@ class ShowViewModel @Inject constructor( fun updateKeyword(newKeyword: String) { _uiState.update { it.copy(keyword = newKeyword) } } - - fun fetchReservationInfo() { - reservationRepository.getReservations() - .map { reservations -> - reservations.firstOrNull { it.reservationState == ReservationState.DEPOSITING } != null - } - .onEach { hasPendingTicket -> - _uiState.update { - it.copy(hasPendingTicket = hasPendingTicket) - } - } - .launchIn(viewModelScope + recordExceptionHandler) - } } diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticketing/TicketingViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticketing/TicketingViewModel.kt index c876f49d..293aecb8 100644 --- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticketing/TicketingViewModel.kt +++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/ticketing/TicketingViewModel.kt @@ -42,7 +42,9 @@ class TicketingViewModel @Inject constructor( val showId: String = requireNotNull(savedStateHandle["showId"]) val salesTicketTypeId: String = requireNotNull(savedStateHandle["salesTicketId"]) private val ticketCount: Int = savedStateHandle["ticketCount"] ?: 1 - private val userId = getUserUsecase().id + private val userId = checkNotNull(getUserUsecase()?.id) { + "[TicketingViewModel] 사용자 정보가 없습니다." + } private val _uiState = MutableStateFlow(TicketingState()) val uiState = _uiState.asStateFlow() diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index e51397a8..024c02be 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ 불티 유저 30일 내에 로그인하여\n계정 삭제가 취소되었어요.\n불티를 다시 찾아주셔서 감사해요! 식별 코드 + 로그인 실패 + 회원가입 실패 계정 삭제