Skip to content
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

화면별 알림 권한 요청 및 채팅방 별로 알림 설정 구현 #139

Open
wants to merge 13 commits into
base: feature/fcm-permission-dialog
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_launcher_foreground" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/white" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package com.sesac.developer_study_platform

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.bumptech.glide.Glide
import com.google.firebase.Firebase
import com.google.firebase.auth.auth
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository
import com.sesac.developer_study_platform.ui.main.MainActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.random.Random

class MyFirebaseMessagingService : FirebaseMessagingService() {

Expand All @@ -21,18 +33,76 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Log.d("fcm", "From: ${remoteMessage.from}")
kotlin.runCatching {
if (remoteMessage.data.isNotEmpty()) {
val uid = remoteMessage.data.getValue("uid")

// Check if message contains a data payload.
if (remoteMessage.data.isNotEmpty()) {
Log.d("fcm", "Message data payload: ${remoteMessage.data}")
createNotificationChannel()
if (uid != Firebase.auth.uid) {
sendNotification(remoteMessage.data)
}
}
}.onFailure {
Log.e("MyFirebaseMessagingService-onMessageReceived", it.message ?: "error occurred.")
}
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val id = getString(R.string.default_notification_channel_id)
val name = getString(R.string.app_name)
val descriptionText = getString(R.string.app_name)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(id, name, importance).apply {
description = descriptionText
}
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

notificationManager.createNotificationChannel(channel)
}
}


private fun sendNotification(data: Map<String, String>) {
kotlin.runCatching {
val randomNumber = Random.nextInt()
val builder = getNotificationBuilder(data, randomNumber)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

notificationManager.notify(randomNumber, builder.build())
}.onFailure {
Log.e("MyFirebaseMessagingService-sendNotification", it.message ?: "error occurred.")
}
}

private fun getNotificationBuilder(
data: Map<String, String>,
randomNumber: Int
): NotificationCompat.Builder {
return NotificationCompat.Builder(this, getString(R.string.default_notification_channel_id))
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(getLargeIcon(data.getValue("imageUrl")))
.setColor(getColor(R.color.white))
.setContentTitle(data.getValue("title"))
.setContentText(data.getValue("text"))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(getPendingIntent(data, randomNumber))
.setAutoCancel(true)
}

private fun getLargeIcon(imageUrl: String): Bitmap {
return Glide.with(this)
.asBitmap()
.load(imageUrl)
.submit()
.get()
}

// Check if message contains a notification payload.
remoteMessage.notification?.let {
Log.d("fcm", "Message Notification Body: ${it.body}")
private fun getPendingIntent(data: Map<String, String>, randomNumber: Int): PendingIntent? {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra("sid", data.getValue("sid"))
}
return PendingIntent.getActivity(this, randomNumber, intent, PendingIntent.FLAG_IMMUTABLE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,15 @@ data class FcmMessage(
@Serializable
data class FcmMessageData(
val token: String = "",
val data: Map<String, String> = mapOf(),
val data: FcmMessageContent,
val android: Map<String, Boolean> = mapOf()
)

@Serializable
data class FcmMessageContent(
val uid: String? = "",
val sid: String? = "",
val title: String = "",
val text: String = "",
val imageUrl: String = "",
)
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ class StudyRepository {
return studyService.getRegistrationIdList(sid)
}

suspend fun deleteRegistrationId(sid: String, registrationId: String) {
studyService.deleteRegistrationId(sid, registrationId)
}

suspend fun deleteNotificationKey(sid: String) {
studyService.deleteNotificationKey(sid)
}

fun getMessageList(sid: String): Flow<Map<String, Message>> = flow {
while (true) {
kotlin.runCatching {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ interface StudyService {
@Path("sid") sid: String
): Map<String, Boolean>

@DELETE("studies/{sid}/registrationIds/{registrationId}.json")
suspend fun deleteRegistrationId(
@Path("sid") sid: String,
@Path("registrationId") registrationId: String
)

@DELETE("studies/{sid}/notificationKey.json")
suspend fun deleteNotificationKey(
@Path("sid") sid: String
)

companion object {
private const val BASE_URL = BuildConfig.FIREBASE_BASE_URL
private val contentType = "application/json".toMediaType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,41 @@ class NotificationPermissionDialogViewModel(private val fcmTokenRepository: FcmT
val moveToMessageEvent: LiveData<Event<String>> = _moveToMessageEvent

fun checkNotificationKey(sid: String) {
viewModelScope.launch {
if (getNotificationKey(sid).isNullOrEmpty()) {
createNotificationKey(sid)
} else if (isRegistrationId(sid, fcmTokenRepository.getToken().first())) {
updateStudyGroup(sid)
}
}
}

private fun createNotificationKey(sid: String) {
viewModelScope.launch {
val token = fcmTokenRepository.getToken().first()
kotlin.runCatching {
fcmRepository.updateStudyGroup(StudyGroup("create", sid, listOf(token)))
}.onSuccess {
addNotificationKey(sid, it.values.first())
val notificationKey = getNotificationKey(sid)
if (!notificationKey.isNullOrEmpty()) {
updateStudyGroup(sid, token, notificationKey)
} else {
createNotificationKey(sid, token)
}
}.onFailure {
Log.e(
"NotificationPermissionDialogViewModel-createNotificationKey",
"NotificationPermissionDialogViewModel-checkNotificationKey",
it.message ?: "error occurred."
)
}
}
}

private fun addNotificationKey(sid: String, notificationKey: String) {
viewModelScope.launch {
private suspend fun getNotificationKey(sid: String): String? {
return viewModelScope.async {
kotlin.runCatching {
studyRepository.addNotificationKey(sid, notificationKey)
}.onSuccess {
addRegistrationId(sid, fcmTokenRepository.getToken().first())
studyRepository.getNotificationKey(sid)
}.onFailure {
Log.e(
"NotificationPermissionDialogViewModel-addNotificationKey",
"NotificationPermissionDialogViewModel-getNotificationKey",
it.message ?: "error occurred."
)
}
}
}.getOrNull()
}.await()
}

private fun updateStudyGroup(sid: String) {
private fun updateStudyGroup(sid: String, token: String, notificationKey: String) {
viewModelScope.launch {
val token = fcmTokenRepository.getToken().first()
kotlin.runCatching {
val notificationKey = getNotificationKey(sid)
if (!notificationKey.isNullOrEmpty()) {
fcmRepository.updateStudyGroup(StudyGroup("add", sid, listOf(token), notificationKey))
}
fcmRepository.updateStudyGroup(StudyGroup("add", sid, listOf(token), notificationKey))
}.onSuccess {
addRegistrationId(sid, token)
}.onFailure {
Expand All @@ -85,47 +72,49 @@ class NotificationPermissionDialogViewModel(private val fcmTokenRepository: FcmT
}
}

private suspend fun getNotificationKey(sid: String): String? {
return viewModelScope.async {
private fun createNotificationKey(sid: String, token: String) {
viewModelScope.launch {
kotlin.runCatching {
studyRepository.getNotificationKey(sid)
fcmRepository.updateStudyGroup(StudyGroup("create", sid, listOf(token)))
}.onSuccess {
addNotificationKey(sid, it.values.first())
}.onFailure {
Log.e(
"NotificationPermissionDialogViewModel-getNotificationKey",
"NotificationPermissionDialogViewModel-createNotificationKey",
it.message ?: "error occurred."
)
}.getOrNull()
}.await()
}
}
}

private fun addRegistrationId(sid: String, registrationId: String) {
private fun addNotificationKey(sid: String, notificationKey: String) {
viewModelScope.launch {
kotlin.runCatching {
studyRepository.addRegistrationId(sid, registrationId)
studyRepository.addNotificationKey(sid, notificationKey)
}.onSuccess {
_checkNotificationKeyEvent.value = Event(Unit)
addRegistrationId(sid, fcmTokenRepository.getToken().first())
}.onFailure {
Log.e(
"NotificationPermissionDialogViewModel-addRegistrationId",
"NotificationPermissionDialogViewModel-addNotificationKey",
it.message ?: "error occurred."
)
}
}
}

private suspend fun isRegistrationId(sid: String, registrationId: String): Boolean {
return viewModelScope.async {
private fun addRegistrationId(sid: String, registrationId: String) {
viewModelScope.launch {
kotlin.runCatching {
studyRepository.getRegistrationIdList(sid)
}.map {
it.containsKey(registrationId)
studyRepository.addRegistrationId(sid, registrationId)
}.onSuccess {
_checkNotificationKeyEvent.value = Event(Unit)
}.onFailure {
Log.e(
"NotificationPermissionDialogViewModel-isRegistrationId",
"NotificationPermissionDialogViewModel-addRegistrationId",
it.message ?: "error occurred."
)
}.getOrDefault(false)
}.await()
}
}
}

fun moveToMessage(sid: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class JoinStudyDialogFragment : DialogFragment() {
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
updateStudyGroup()
checkNotificationKey()
} else {
Toast.makeText(context, getString(R.string.all_notification_info), Toast.LENGTH_SHORT).show()
viewModel.moveToMessage(args.study.sid)
Expand Down Expand Up @@ -106,7 +106,7 @@ class JoinStudyDialogFragment : DialogFragment() {
requireContext(),
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED -> {
updateStudyGroup()
checkNotificationKey()
}

shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> {
Expand All @@ -118,13 +118,13 @@ class JoinStudyDialogFragment : DialogFragment() {
}
}
} else {
updateStudyGroup()
checkNotificationKey()
}
}

private fun updateStudyGroup() {
viewModel.updateStudyGroup(args.study.sid)
viewModel.updateStudyGroupEvent.observe(
private fun checkNotificationKey() {
viewModel.checkNotificationKey(args.study.sid)
viewModel.checkNotificationKeyEvent.observe(
viewLifecycleOwner,
EventObserver {
viewModel.moveToMessage(args.study.sid)
Expand Down
Loading