Skip to content

Commit

Permalink
Merge pull request #389 from sopt-makers/feature/yxnsx/#341-notification
Browse files Browse the repository at this point in the history
[Feature/yxnsx/#341-notification] 푸시 알림 기능 추가
  • Loading branch information
yxnsx committed Oct 9, 2023
2 parents 1a5c03b + 0f1303a commit 987b2e4
Show file tree
Hide file tree
Showing 54 changed files with 1,631 additions and 74 deletions.
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

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

<queries>
Expand Down Expand Up @@ -103,7 +104,12 @@
<data android:pathPattern="/oauth2redirect" />
</intent-filter>
</activity>

<activity
android:name=".feature.notification.NotificationHistoryActivity"
android:exported="false" />
<activity
android:name=".feature.notification.NotificationDetailActivity"
android:exported="false" />
<service
android:name=".config.messaging.SoptFirebaseMessagingService"
android:exported="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sopt.official.config.messaging

enum class RemoteMessageLinkType {
WEB_LINK, DEEP_LINK, DEFAULT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sopt.official.config.messaging

enum class RemoteMessageType {
NOTICE, NEWS
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
package org.sopt.official.config.messaging

import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.sopt.official.R
import org.sopt.official.data.persistence.SoptDataStore
import org.sopt.official.domain.entity.auth.UserStatus
import org.sopt.official.domain.usecase.notification.RegisterPushTokenUseCase
import org.sopt.official.feature.auth.AuthActivity
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -11,11 +26,89 @@ class SoptFirebaseMessagingService : FirebaseMessagingService() {
@Inject
lateinit var dataStore: SoptDataStore

@Inject
lateinit var registerPushTokenUseCase: RegisterPushTokenUseCase

private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)

override fun onNewToken(token: String) {
dataStore.pushToken = token
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
// sendRegistrationToServer(token)
if (dataStore.userStatus == UserStatus.UNAUTHENTICATED.name) return
scope.launch {
dataStore.pushToken = token
registerPushTokenUseCase.invoke(token)
}
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
if (remoteMessage.data.isEmpty()) return

val receivedData = remoteMessage.data
val title = receivedData["title"] ?: ""
val body = receivedData["content"] ?: ""
val webLink = receivedData["webLink"] ?: ""
val deepLink = receivedData["deepLink"] ?: ""

val notificationId = System.currentTimeMillis().toInt()
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setStyle(NotificationCompat.BigTextStyle().bigText(body))
.setSmallIcon(R.drawable.img_logo_small)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setChannelId(getString(R.string.toolbar_notification))
.setAutoCancel(true)

notificationBuilder.setNotificationContentIntent(
if (webLink.isNotBlank()) {
RemoteMessageLinkType.WEB_LINK
} else if (deepLink.isNotBlank()) {
RemoteMessageLinkType.DEEP_LINK
} else {
RemoteMessageLinkType.DEFAULT
},
webLink.ifBlank { deepLink.ifBlank { "" } },
notificationId
)

val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId, notificationBuilder.build())
}

private fun NotificationCompat.Builder.setNotificationContentIntent(
remoteMessageLinkType: RemoteMessageLinkType,
link: String,
notificationId: Int
): NotificationCompat.Builder {
val intent = Intent(this@SoptFirebaseMessagingService, AuthActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(REMOTE_MESSAGE_EVENT_TYPE, remoteMessageLinkType.name)
putExtra(REMOTE_MESSAGE_EVENT_LINK, link)
}

return this.setContentIntent(
PendingIntent.getActivity(
this@SoptFirebaseMessagingService,
notificationId,
intent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
)
)
}

companion object {
const val NOTIFICATION_CHANNEL_ID = "SOPT"
const val REMOTE_MESSAGE_EVENT_TYPE = "REMOTE_MESSAGE_EVENT_TYPE"
const val REMOTE_MESSAGE_EVENT_LINK = "REMOTE_MESSAGE_EVENT_LINK"
}

override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.sopt.official.data.mapper

import org.sopt.official.data.model.notification.response.NotificationHistoryItemResponse
import org.sopt.official.domain.entity.notification.NotificationHistoryItem

class NotificationHistoryItemMapper {
fun toNotificationHistoryItem(responseItem: NotificationHistoryItemResponse): NotificationHistoryItem {
return NotificationHistoryItem(
notificationId = responseItem.notificationId,
userId = responseItem.userId,
title = responseItem.title,
content = responseItem.content,
category = responseItem.category,
isRead = responseItem.isRead,
createdAt = responseItem.createdAt
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ data class HomeResponse(
val user: HomeUserResponse,
@SerialName("operation")
val operation: HomeOperationResponse,
@SerialName("isAllConfirm")
val isAllConfirm: Boolean
) {

fun toEntity(): SoptUser = SoptUser(
user = this.user.toEntity(),
operation = this.operation.toEntity(),
isAllConfirm = this.isAllConfirm
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.official.data.model.notification.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class UpdatePushTokenRequest(
@SerialName("platform")
val platform: String,
@SerialName("pushToken")
val pushToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.sopt.official.data.model.notification.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class NotificationDetailResponse(
@SerialName("notificationId")
val notificationId: Long,
@SerialName("userId")
val userId: Int,
@SerialName("title")
val title: String,
@SerialName("content")
val content: String?,
@SerialName("deepLink")
val deepLink: String?,
@SerialName("webLink")
val webLink: String?,
@SerialName("createdAt")
val createdAt: String,
@SerialName("updatedAt")
val updatedAt: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sopt.official.data.model.notification.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class NotificationHistoryItemResponse(
@SerialName("notificationId")
val notificationId: Long,
@SerialName("userId")
val userId: Int,
@SerialName("title")
val title: String,
@SerialName("content")
val content: String?,
@SerialName("category")
val category: String,
@SerialName("isRead")
val isRead: Boolean,
@SerialName("createdAt")
val createdAt: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.sopt.official.data.model.notification.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class NotificationReadingStateResponse(
@SerialName("id")
val id: Int,
@SerialName("isRead")
val isRead: Boolean,
@SerialName("createdAt")
val createdAt: String,
@SerialName("updatedAt")
val updatedAt: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.official.data.model.notification.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class UpdatePushTokenResponse(
@SerialName("status")
val status: Int,
@SerialName("success")
val success: Boolean,
@SerialName("message")
val message: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable

@Serializable
data class AuthRequest(
@SerialName("code") val code: String,
@SerialName("pushToken") val pushToken: String
@SerialName("code")
val code: String,
@SerialName("pushToken")
val pushToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.official.data.model.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LogOutRequest(
@SerialName("platform") val platform: String,
@SerialName("pushToken") val pushToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.official.data.model.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LogOutResponse(
@SerialName("success")
val success: Boolean,
@SerialName("message")
val message: String
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.sopt.official.data.repository

import org.sopt.official.common.di.Auth
import org.sopt.official.data.model.request.LogOutRequest
import org.sopt.official.data.model.request.RefreshRequest
import org.sopt.official.data.model.response.LogOutResponse
import org.sopt.official.data.persistence.SoptDataStore
import org.sopt.official.data.service.AuthService
import org.sopt.official.domain.entity.auth.Token
Expand Down Expand Up @@ -36,7 +38,14 @@ class AuthRepositoryImpl @Inject constructor(
service.withdraw()
}

override suspend fun logout() = runCatching {
dataStore.clear()
override suspend fun logout(
pushToken: String
): Result<LogOutResponse> = runCatching {
service.logOut(
LogOutRequest(
platform = "Android",
pushToken = pushToken
)
)
}
}
Loading

0 comments on commit 987b2e4

Please sign in to comment.