Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ class NotificationService : FirebaseMessagingService() {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)

val builder = NotificationCompat.Builder(context, BACKGROUND_CHANNEL)
val builder = NotificationCompat.Builder(context, NOTICE_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.primary))
.setContentTitle(title)
.setContentText(body)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)

val notificationManager: NotificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
Expand All @@ -91,22 +93,26 @@ class NotificationService : FirebaseMessagingService() {
}

companion object {
// 기존 ID
private const val BACKGROUND_CHANNEL = "백그라운드 알림"
private const val BACKGROUND_CHANNEL_DESCRIPTION = "백그라운드 알림을 관리하는 채널입니다."

// 중요 알림
private const val NOTICE_CHANNEL_ID = "piece_official_notice"
private const val NOTICE_CHANNEL_NAME = "공지 및 주요 알림"

fun createNotificationChannel(context: Context) {
val channelId = BACKGROUND_CHANNEL
val channelName = BACKGROUND_CHANNEL
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

val channel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_DEFAULT
NOTICE_CHANNEL_ID,
NOTICE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = BACKGROUND_CHANNEL_DESCRIPTION
description = "서비스의 중요한 공지와 알림을 전달합니다."
setShowBadge(true)
}

val notificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.puzzle.data.repository

import com.puzzle.datastore.datasource.profile.LocalProfileDataSource
import com.puzzle.datastore.datasource.token.LocalTokenDataSource
import com.puzzle.datastore.datasource.user.LocalUserDataSource
import com.puzzle.domain.model.auth.OAuthProvider
Expand All @@ -17,6 +18,7 @@ class AuthRepositoryImpl @Inject constructor(
private val notificationDataSource: NotificationDataSource,
private val localTokenDataSource: LocalTokenDataSource,
private val localUserDataSource: LocalUserDataSource,
private val localProfileDataSource: LocalProfileDataSource,
) : AuthRepository {
override suspend fun loginOauth(
oAuthProvider: OAuthProvider,
Expand Down Expand Up @@ -46,27 +48,21 @@ class AuthRepositoryImpl @Inject constructor(

override suspend fun logout() {
coroutineScope {
val clearUserRoleJob = launch { localUserDataSource.clearUserRole() }
val clearLastMatchingPoolEmptyDialogShownDateJob = launch { localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate() }
val clearTokenJob = launch { localTokenDataSource.clearToken() }

clearTokenJob.join()
clearLastMatchingPoolEmptyDialogShownDateJob.join()
clearUserRoleJob.join()
launch { localUserDataSource.clearUserRole() }
launch { localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate() }
launch { localTokenDataSource.clearToken() }
launch { localProfileDataSource.clearMyProfile() }
}
}

override suspend fun withdraw(reason: String) {
authDataSource.withdraw(reason)

coroutineScope {
val clearUserRoleJob = launch { localUserDataSource.clearUserRole() }
val clearLastMatchingPoolEmptyDialogShownDateJob = launch { localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate() }
val clearTokenJob = launch { localTokenDataSource.clearToken() }

clearTokenJob.join()
clearLastMatchingPoolEmptyDialogShownDateJob.join()
clearUserRoleJob.join()
launch { localUserDataSource.clearUserRole() }
launch { localUserDataSource.clearLastMatchingPoolEmptyDialogShownDate() }
launch { localTokenDataSource.clearToken() }
launch { localProfileDataSource.clearMyProfile() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ class ProfileRepositoryImpl @Inject constructor(
)
}

override suspend fun clearMyProfile() {
localProfileDataSource.clearMyProfile()
}

override suspend fun connectSSE() = sseClient.connect()
override suspend fun disconnectSSE() {
profileDataSource.disconnectSSE()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.puzzle.data.repository

import com.puzzle.data.source.auth.FakeAuthDataSource
import com.puzzle.data.source.notification.SpyNotificationDataSource
import com.puzzle.data.source.profile.FakeLocalProfileDataSource
import com.puzzle.data.source.token.FakeLocalTokenDataSource
import com.puzzle.data.source.user.FakeLocalUserDataSource
import com.puzzle.domain.model.auth.OAuthProvider
Expand All @@ -17,6 +18,8 @@ class AuthRepositoryImplTest {
private lateinit var notificationDataSource: SpyNotificationDataSource
private lateinit var localTokenDataSource: FakeLocalTokenDataSource
private lateinit var localUserDataSource: FakeLocalUserDataSource
private lateinit var localProfileDataSource: FakeLocalProfileDataSource

private lateinit var authRepository: AuthRepositoryImpl

@BeforeEach
Expand All @@ -25,11 +28,13 @@ class AuthRepositoryImplTest {
notificationDataSource = SpyNotificationDataSource()
localTokenDataSource = FakeLocalTokenDataSource()
localUserDataSource = FakeLocalUserDataSource()
localProfileDataSource = FakeLocalProfileDataSource()
authRepository = AuthRepositoryImpl(
authDataSource = authDataSource,
notificationDataSource = notificationDataSource,
localTokenDataSource = localTokenDataSource,
localUserDataSource = localUserDataSource,
localProfileDataSource = localProfileDataSource
)
}

Expand Down Expand Up @@ -68,6 +73,7 @@ class AuthRepositoryImplTest {
assertTrue(localTokenDataSource.refreshToken.first().isEmpty())
assertTrue(localUserDataSource.userRole.first().isEmpty())
assertTrue(localUserDataSource.lastMatchingPoolEmptyDialogShownDate.first().isEmpty())
assertTrue(localProfileDataSource.myValueTalks.first().isNullOrEmpty())
}

@Test
Expand All @@ -83,5 +89,6 @@ class AuthRepositoryImplTest {
assertTrue(localTokenDataSource.refreshToken.first().isEmpty())
assertTrue(localUserDataSource.userRole.first().isEmpty())
assertTrue(localUserDataSource.lastMatchingPoolEmptyDialogShownDate.first().isEmpty())
assertTrue(localProfileDataSource.myValueTalks.first().isNullOrEmpty())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ interface ProfileRepository {
valueTalks: List<ValueTalkAnswer>
)

suspend fun clearMyProfile()

suspend fun connectSSE()
suspend fun disconnectSSE()
}
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class RegisterProfileViewModel @Inject constructor(
},
)
}.onSuccess {
profileRepository.clearMyProfile()
loadMyProfile()
setState { copy(currentPage = RegisterProfileState.Page.COMPLETE) }
}.onFailure { exception ->
Expand Down Expand Up @@ -336,6 +337,7 @@ class RegisterProfileViewModel @Inject constructor(
},
)
}.onSuccess {
profileRepository.clearMyProfile()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

플로우를 잘 모르겠어서 질문드리는데 uploadProfile 성공 시 clearMyProfile 하고있는데 이건 api와 로컬인가요? 어떤관계인지??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 유저의 가치관 픽, 톡은 데이터스토어로 캐싱을 합니다.

그리고 캐싱이 되어있지 않은 상태에서 load를 해야한다면 api 호출 -> 데이터 스토어 저장 -> 데이터 스토어 값 load 입니다.

업로드 프로필(PUT)을 하면 서버에 변경 요청을 하지만 기존에 데이터 스토어 값이 그대로 남아있어 다른 곳에서 load할 때 이전 값을 사용합니다.

그래서 추가적으로 토큰 만료, 로그아웃, 탈퇴 시 clear를 해줘야 서버의 데이터 동기화가 가능합니다.

로컬 캐싱 관련 로직 미리 봐두시면 좋을 거 같네요

loadMyProfile()
setState { copy(currentPage = RegisterProfileState.Page.COMPLETE) }
}.onFailure { exception ->
Expand Down
23 changes: 11 additions & 12 deletions presentation/src/main/java/com/puzzle/presentation/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.puzzle.navigation.AuthGraphBaseRoute
import com.puzzle.navigation.MatchingGraph
import com.puzzle.navigation.NavigationEvent.TopLevelTo
import com.puzzle.navigation.NavigationHelper
import com.puzzle.navigation.OnboardingRoute
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -79,15 +78,7 @@ class MainViewModel @Inject constructor(

private fun handleHttpError(exception: HttpResponseException) {
when (exception.errorCode) {
PieceErrorCode.EXPIRED_REFRESH_TOKEN -> {
viewModelScope.launch {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()

navigationHelper.navigate(
TopLevelTo(AuthGraphBaseRoute)
)
}
}
PieceErrorCode.EXPIRED_REFRESH_TOKEN -> clearAndNavigateToAuth()

PieceErrorCode.PERMANENTLY_BANNED -> eventHelper.sendEvent(PieceEvent.ShowBannedDialog)

Expand Down Expand Up @@ -144,8 +135,7 @@ class MainViewModel @Inject constructor(
(exception is IllegalArgumentException ||
(exception is HttpResponseException && exception.status == HttpResponseStatus.Unauthorized)
) -> {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
navigationHelper.navigate(TopLevelTo(route = OnboardingRoute))
clearAndNavigateToAuth()
return
}

Expand All @@ -172,4 +162,13 @@ class MainViewModel @Inject constructor(
notificationRepository.readNotification(notificationId)
}.onFailure { errorHelper.sendError(it) }
}

private fun clearAndNavigateToAuth() = viewModelScope.launch {
suspendRunCatching {
userRepository.clearLastMatchingPoolEmptyDialogShownDate()
profileRepository.clearMyProfile()
}.onFailure { errorHelper.recordError(it) }

navigationHelper.navigate(TopLevelTo(AuthGraphBaseRoute))
}
}