-
Notifications
You must be signed in to change notification settings - Fork 3
[FEAT] FCM Token 발급, 등록, 푸시알림 기능 구현 및 기타 QA수정 #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d627f7f
381ed36
7300b20
0230ab9
8db0e02
8539a5d
e8bfcc7
66e1319
b8be3b7
7bde62e
8ce347d
392b15f
e206951
530aeba
513e726
256fc22
28404be
bcbccd2
057ea94
a179a34
c132fbd
eb78a97
9bde90d
50cda6e
02cc000
5a9a01b
4cc7b3b
9009d24
4cf17db
9f23504
02fda02
f578aef
1dcbf42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package com.texthip.thip.data.manager | ||
|
|
||
| import android.content.Context | ||
| import android.util.Log | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import androidx.datastore.preferences.core.edit | ||
| import androidx.datastore.preferences.core.stringPreferencesKey | ||
| import com.google.firebase.messaging.FirebaseMessaging | ||
| import com.texthip.thip.data.repository.NotificationRepository | ||
| import com.texthip.thip.utils.auth.getAppScopeDeviceId | ||
| import com.texthip.thip.utils.permission.NotificationPermissionUtils | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.coroutines.flow.map | ||
| import kotlinx.coroutines.suspendCancellableCoroutine | ||
| import kotlin.coroutines.resume | ||
| import kotlin.coroutines.resumeWithException | ||
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
|
|
||
| @Singleton | ||
| class FcmTokenManager @Inject constructor( | ||
| private val dataStore: DataStore<Preferences>, | ||
| private val notificationRepository: NotificationRepository, | ||
| @param:ApplicationContext private val context: Context | ||
| ) { | ||
|
|
||
| companion object { | ||
| private val FCM_TOKEN_KEY = stringPreferencesKey("fcm_token") | ||
| } | ||
|
|
||
| suspend fun handleNewToken(newToken: String) { | ||
| val storedToken = getFcmTokenOnce() | ||
|
|
||
| if (storedToken != newToken) { | ||
| Log.d("FCM", "Token updated") | ||
|
|
||
| saveFcmToken(newToken) | ||
| sendTokenToServer(newToken) | ||
| } | ||
| } | ||
|
|
||
| suspend fun sendCurrentTokenIfExists() { | ||
| val storedFcmToken = getFcmTokenOnce() | ||
|
|
||
| if (storedFcmToken != null) { | ||
| sendTokenToServer(storedFcmToken) | ||
| } else { | ||
| // 저장된 토큰이 없으면 Firebase에서 직접 가져와서 저장하고 전송 | ||
| try { | ||
| val token = fetchCurrentToken() | ||
| saveFcmToken(token) | ||
| sendTokenToServer(token) | ||
| } catch (e: Exception) { | ||
| Log.e("FCM", "Failed to fetch and send current token", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private suspend fun fetchCurrentToken(): String = suspendCancellableCoroutine { continuation -> | ||
| try { | ||
| FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> | ||
| when { | ||
| task.isSuccessful -> { | ||
| val token = task.result | ||
| if (token != null) { | ||
| continuation.resume(token) | ||
| } else { | ||
| continuation.resumeWithException(IllegalStateException("FCM token is null")) | ||
| } | ||
| } | ||
| else -> { | ||
| val exception = task.exception ?: Exception("Unknown error fetching FCM token") | ||
| Log.w("FCM", "Failed to fetch token", exception) | ||
| continuation.resumeWithException(exception) | ||
| } | ||
| } | ||
| } | ||
| } catch (e: Exception) { | ||
| Log.e("FCM", "Error fetching FCM token", e) | ||
| continuation.resumeWithException(e) | ||
| } | ||
| } | ||
|
|
||
| // FCM 토큰 로컬 저장 관리 | ||
| private suspend fun saveFcmToken(token: String) { | ||
| dataStore.edit { prefs -> prefs[FCM_TOKEN_KEY] = token } | ||
| } | ||
|
|
||
| private suspend fun getFcmTokenOnce(): String? { | ||
| return dataStore.data.map { prefs -> prefs[FCM_TOKEN_KEY] }.first() | ||
| } | ||
|
|
||
| private suspend fun sendTokenToServer(token: String) { | ||
| // 알림 권한이 없으면 토큰을 서버에 전송하지 않음 | ||
| if (!NotificationPermissionUtils.isNotificationPermissionGranted(context)) { | ||
| Log.w("FCM", "Notification permission not granted, skipping token registration") | ||
| return | ||
| } | ||
|
|
||
| val deviceId = context.getAppScopeDeviceId() | ||
| notificationRepository.registerFcmToken(deviceId, token) | ||
| .onSuccess { | ||
| Log.d("FCM", "Token sent successfully") | ||
| } | ||
| .onFailure { exception -> | ||
| Log.e("FCM", "Failed to send token", exception) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,6 @@ | ||||||||||||||||||||||||||||||||||
| package com.texthip.thip.data.manager | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * 도서 장르를 나타내는 enum class | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| /** | |
| * Represents the genre category for content in the application. | |
| * | |
| * @property displayKey The key used for display and identification. | |
| * @property apiCategory The category name used in the API. | |
| * @property networkApiCategory The category name used for network API calls (defaults to [apiCategory]). | |
| * | |
| * Enum values: | |
| * - LITERATURE: Literature genre. | |
| * - SCIENCE_IT: Science and IT genre. | |
| * - SOCIAL_SCIENCE: Social science genre. | |
| * - HUMANITIES: Humanities genre. | |
| * - ART: Art genre. | |
| */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ class TokenManager @Inject constructor( | |
| companion object { | ||
| private val APP_TOKEN_KEY = stringPreferencesKey("app_token") // 정식 액세스토큰 | ||
| private val TEMP_TOKEN_KEY = stringPreferencesKey("temp_token") // 임시 토큰 | ||
| private val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token") | ||
| //private val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token") | ||
|
||
| } | ||
|
|
||
| // ====== 정식 토큰 ====== | ||
|
|
@@ -54,13 +54,13 @@ class TokenManager @Inject constructor( | |
| } | ||
|
|
||
| // ====== Refresh 토큰 (추후 확장용) ====== | ||
| suspend fun saveRefreshToken(token: String) { | ||
| /*suspend fun saveRefreshToken(token: String) { | ||
| dataStore.edit { prefs -> prefs[REFRESH_TOKEN_KEY] = token } | ||
| } | ||
|
|
||
| suspend fun getRefreshTokenOnce(): String? { | ||
| return dataStore.data.map { prefs -> prefs[REFRESH_TOKEN_KEY] }.first() | ||
| } | ||
| }*/ | ||
|
|
||
| suspend fun clearTokens() { | ||
| dataStore.edit { prefs -> prefs.clear() } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.texthip.thip.data.model.notification.request | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class FcmTokenDeleteRequest( | ||
| val deviceId: String | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.texthip.thip.data.model.notification.request | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class FcmTokenRequest( | ||
| val deviceId: String, | ||
| val fcmToken: String, | ||
| val platformType: String = "ANDROID" | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.texthip.thip.data.model.notification.request | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class NotificationEnabledRequest( | ||
| val enable: Boolean, | ||
| val deviceId: String | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.texthip.thip.data.model.notification.response | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class NotificationEnabledResponse( | ||
| val isEnabled: Boolean | ||
| ) |
Uh oh!
There was an error while loading. Please reload this page.