Skip to content

Commit

Permalink
Merge pull request #305 from team-peekabook/feature/#295-firebase
Browse files Browse the repository at this point in the history
#295 [feat] 푸시알림 구현
  • Loading branch information
2zerozu authored Mar 11, 2024
2 parents a668104 + 2ca03e6 commit ce4af6e
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 10 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ dependencies {
// firebase
implementation platform('com.google.firebase:firebase-bom:32.6.0')
implementation 'com.google.android.gms:play-services-tagmanager:18.0.4'
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx:23.4.1'

implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />

<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
Expand Down Expand Up @@ -139,6 +141,14 @@
android:scheme="${KAKAO_REDIRECT_SCHEME}" />
</intent-filter>
</activity>

<service
android:name=".PeekaMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>

</manifest>
68 changes: 68 additions & 0 deletions app/src/main/java/com/sopt/peekabookaos/PeekaMessageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sopt.peekabookaos

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.sopt.peekabookaos.presentation.splash.SplashActivity
import timber.log.Timber

class PeekaMessageService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Timber.d("Refreshed token: $token")
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
if (remoteMessage.data.isNotEmpty()) {
val title = remoteMessage.data[TITLE] ?: ""
val body = remoteMessage.data[BODY] ?: ""
sendNotification(title, body)
}
}

private fun sendNotification(title: String, body: String) {
createNotificationChannel()
val intent = Intent(this, SplashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notificationBuilder = Notification.Builder(this, CHANNEL_ID)
.setContentTitle(title)
.setSmallIcon(R.mipmap.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setContentText(body)
.setAutoCancel(true)

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}

private fun createNotificationChannel() {
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel =
NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)

notificationManager.createNotificationChannel(channel)
}

companion object {
const val CHANNEL_ID = "peeka_channel"
const val NOTIFICATION_ID = 1
const val CHANNEL_NAME = "peeka_channel_name"
const val TITLE = "title"
const val BODY = "body"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
val socialPlatform: String
val socialPlatform: String,
val fcmToken: String
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.sopt.peekabookaos.data.repository

import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import com.sopt.peekabookaos.data.source.local.LocalPrefDataSource
import com.sopt.peekabookaos.data.source.local.LocalSignedUpDataSource
import com.sopt.peekabookaos.data.source.local.LocalTokenDataSource
import com.sopt.peekabookaos.data.source.remote.AuthDataSource
import com.sopt.peekabookaos.domain.entity.Token
import com.sopt.peekabookaos.domain.repository.AuthRepository
import timber.log.Timber
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
Expand All @@ -14,8 +17,8 @@ class AuthRepositoryImpl @Inject constructor(
private val localPrefDataSource: LocalPrefDataSource,
private val localSignedUpDataSource: LocalSignedUpDataSource
) : AuthRepository {
override suspend fun postLogin(socialPlatform: String): Result<Token> =
kotlin.runCatching { authDataSource.postLogin(socialPlatform) }.map { response ->
override suspend fun postLogin(socialPlatform: String, fcmToken: String): Result<Token> =
kotlin.runCatching { authDataSource.postLogin(socialPlatform, fcmToken) }.map { response ->
requireNotNull(response.data).toToken()
}

Expand All @@ -33,4 +36,16 @@ class AuthRepositoryImpl @Inject constructor(
override fun setSignedUp() {
localSignedUpDataSource.isSignedUp = true
}

override fun getFcmToken(setFcmToken: (String) -> Unit) {
FirebaseMessaging.getInstance().token.addOnCompleteListener(
OnCompleteListener { task ->
if (!task.isSuccessful) {
Timber.e("Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
setFcmToken(task.result)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ class LocalTokenDataSource @Inject constructor(
companion object {
private const val ACCESS_TOKEN = "access_token"
private const val REFRESH_TOKEN = "refresh_token"
private const val FCM_TOKEN = "fcm_token"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import javax.inject.Inject
class AuthDataSource @Inject constructor(
private val authService: AuthService
) {
suspend fun postLogin(socialPlatform: String): BaseResponse<LoginResponse> =
authService.postLogin(LoginRequest(socialPlatform))
suspend fun postLogin(socialPlatform: String, fcmToken: String): BaseResponse<LoginResponse> =
authService.postLogin(LoginRequest(socialPlatform, fcmToken))

suspend fun deleteUser(): NoResponse = authService.deleteUser()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package com.sopt.peekabookaos.domain.entity
data class Token(
val accessToken: String = "",
val refreshToken: String = "",
val fcmToke: String = "",
val isSignedUp: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.sopt.peekabookaos.domain.repository
import com.sopt.peekabookaos.domain.entity.Token

interface AuthRepository {
suspend fun postLogin(socialPlatform: String): Result<Token>
suspend fun postLogin(socialPlatform: String, fcmToken: String): Result<Token>

suspend fun deleteUser(): Result<Unit>

Expand All @@ -14,4 +14,6 @@ interface AuthRepository {
fun getSignedUp(): Boolean

fun setSignedUp()

fun getFcmToken(setFcmToken: (String) -> Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sopt.peekabookaos.domain.usecase

import com.sopt.peekabookaos.domain.repository.AuthRepository
import javax.inject.Inject

class GetFcmTokenUseCase @Inject constructor(
private val authRepository: AuthRepository
) {
operator fun invoke(setFcmToken: (String) -> Unit) =
authRepository.getFcmToken(setFcmToken)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import javax.inject.Inject
class PostLoginUseCase @Inject constructor(
private val authRepository: AuthRepository
) {
suspend operator fun invoke(socialPlatform: String) = authRepository.postLogin(socialPlatform)
suspend operator fun invoke(socialPlatform: String, fcmToken: String) =
authRepository.postLogin(socialPlatform, fcmToken)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sopt.peekabookaos.presentation.socialLogin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kakao.sdk.auth.model.OAuthToken
import com.sopt.peekabookaos.domain.usecase.GetFcmTokenUseCase
import com.sopt.peekabookaos.domain.usecase.InitTokenUseCase
import com.sopt.peekabookaos.domain.usecase.PostLoginUseCase
import com.sopt.peekabookaos.util.KakaoLoginCallback
Expand All @@ -18,8 +19,13 @@ import javax.inject.Inject
@HiltViewModel
class SocialLoginViewModel @Inject constructor(
private val postLoginUseCase: PostLoginUseCase,
private val initTokenUseCase: InitTokenUseCase
private val initTokenUseCase: InitTokenUseCase,
private val getFcmTokenUseCase: GetFcmTokenUseCase
) : ViewModel() {
init {
getFcmToken()
}

private val _isKakaoLogin = MutableStateFlow(false)
val isKakaoLogin = _isKakaoLogin.asStateFlow()

Expand All @@ -33,9 +39,11 @@ class SocialLoginViewModel @Inject constructor(
}.handleResult(token, error)
}

private var fcmToken = ""

fun postLogin() {
viewModelScope.launch {
postLoginUseCase(SOCIAL_TYPE)
postLoginUseCase(SOCIAL_TYPE, fcmToken)
.onSuccess { response ->
initTokenUseCase(response.accessToken, response.refreshToken)
_isSignedUp.emit(response.isSignedUp)
Expand All @@ -45,6 +53,10 @@ class SocialLoginViewModel @Inject constructor(
}
}

private fun getFcmToken() {
getFcmTokenUseCase { getFcmToken -> fcmToken = getFcmToken }
}

companion object {
private const val SOCIAL_TYPE = "kakao"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import javax.inject.Inject
@HiltViewModel
class SplashViewModel @Inject constructor(
private val hasUpdateVersionCheckUseCase: HasUpdateVersionCheckUseCase,
private val getSignedUpUseCase: GetSignedUpUseCase
private val getSignedUpUseCase: GetSignedUpUseCase,
) : ViewModel() {
private val _uiState = MutableStateFlow<SplashUiState>(SplashUiState.Idle)
val uiState = _uiState.asSharedFlow()
Expand Down

0 comments on commit ce4af6e

Please sign in to comment.