219 lines
9.0 KiB
Kotlin
219 lines
9.0 KiB
Kotlin
package com.rosetta.messenger.push
|
||
|
||
import android.app.NotificationChannel
|
||
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 com.rosetta.messenger.MainActivity
|
||
import com.rosetta.messenger.R
|
||
import com.rosetta.messenger.data.AccountManager
|
||
import com.rosetta.messenger.data.PreferencesManager
|
||
import kotlinx.coroutines.CoroutineScope
|
||
import kotlinx.coroutines.Dispatchers
|
||
import kotlinx.coroutines.SupervisorJob
|
||
import kotlinx.coroutines.runBlocking
|
||
|
||
/**
|
||
* Firebase Cloud Messaging Service для обработки push-уведомлений
|
||
*
|
||
* Обрабатывает:
|
||
* - Получение нового FCM токена
|
||
* - Получение push-уведомлений о новых сообщениях
|
||
* - Отображение уведомлений
|
||
*/
|
||
class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
||
|
||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||
|
||
companion object {
|
||
private const val TAG = "RosettaFCM"
|
||
private const val CHANNEL_ID = "rosetta_messages"
|
||
private const val CHANNEL_NAME = "Messages"
|
||
|
||
// 🔥 Флаг - приложение в foreground (видимо пользователю)
|
||
@Volatile var isAppInForeground = false
|
||
|
||
/** Уникальный notification ID для каждого чата (по publicKey) */
|
||
fun getNotificationIdForChat(senderPublicKey: String): Int {
|
||
return senderPublicKey.hashCode() and 0x7FFFFFFF // positive int
|
||
}
|
||
|
||
/** Убрать уведомление конкретного чата из шторки */
|
||
fun cancelNotificationForChat(context: Context, senderPublicKey: String) {
|
||
val notificationManager =
|
||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||
notificationManager.cancel(getNotificationIdForChat(senderPublicKey))
|
||
}
|
||
}
|
||
|
||
/** Вызывается когда получен новый FCM токен Отправляем его на сервер через протокол */
|
||
override fun onNewToken(token: String) {
|
||
super.onNewToken(token)
|
||
|
||
// Сохраняем токен локально
|
||
saveFcmToken(token)
|
||
|
||
// 📤 Токен будет отправлен на сервер после успешного логина в MainActivity
|
||
}
|
||
|
||
/** Вызывается когда получено push-уведомление */
|
||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||
super.onMessageReceived(remoteMessage)
|
||
|
||
// Обрабатываем data payload
|
||
remoteMessage.data.isNotEmpty().let {
|
||
val type = remoteMessage.data["type"]
|
||
val senderPublicKey = remoteMessage.data["sender_public_key"]
|
||
val senderName =
|
||
remoteMessage.data["sender_name"] ?: senderPublicKey?.take(10) ?: "Unknown"
|
||
val messagePreview = remoteMessage.data["message_preview"] ?: "New message"
|
||
|
||
when (type) {
|
||
"new_message" -> {
|
||
// Показываем уведомление о новом сообщении
|
||
showMessageNotification(senderPublicKey, senderName, messagePreview)
|
||
}
|
||
"message_read" -> {
|
||
// Сообщение прочитано - можно обновить UI если приложение открыто
|
||
}
|
||
else -> {}
|
||
}
|
||
}
|
||
|
||
// Обрабатываем notification payload (если есть)
|
||
remoteMessage.notification?.let {
|
||
showSimpleNotification(it.title ?: "Rosetta", it.body ?: "New message")
|
||
}
|
||
}
|
||
|
||
/** Показать уведомление о новом сообщении */
|
||
private fun showMessageNotification(
|
||
senderPublicKey: String?,
|
||
senderName: String,
|
||
messagePreview: String
|
||
) {
|
||
// 🔥 Не показываем уведомление если приложение открыто
|
||
if (isAppInForeground) {
|
||
return
|
||
}
|
||
val senderKey = senderPublicKey?.trim().orEmpty()
|
||
if (senderKey.isNotEmpty() && isDialogMuted(senderKey)) {
|
||
return
|
||
}
|
||
|
||
createNotificationChannel()
|
||
|
||
val notifId = getNotificationIdForChat(senderPublicKey ?: "")
|
||
|
||
// Intent для открытия чата
|
||
val intent =
|
||
Intent(this, MainActivity::class.java).apply {
|
||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||
putExtra("open_chat", senderPublicKey)
|
||
}
|
||
|
||
val pendingIntent =
|
||
PendingIntent.getActivity(
|
||
this,
|
||
notifId,
|
||
intent,
|
||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||
)
|
||
|
||
val notification =
|
||
NotificationCompat.Builder(this, CHANNEL_ID)
|
||
.setSmallIcon(R.drawable.ic_notification)
|
||
.setContentTitle(senderName)
|
||
.setContentText(messagePreview)
|
||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||
.setAutoCancel(true)
|
||
.setContentIntent(pendingIntent)
|
||
.build()
|
||
|
||
val notificationManager =
|
||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||
notificationManager.notify(notifId, notification)
|
||
}
|
||
|
||
/** Показать простое уведомление */
|
||
private fun showSimpleNotification(title: String, body: String) {
|
||
// 🔥 Не показываем уведомление если приложение открыто
|
||
if (isAppInForeground) {
|
||
return
|
||
}
|
||
|
||
createNotificationChannel()
|
||
|
||
val intent =
|
||
Intent(this, MainActivity::class.java).apply {
|
||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||
}
|
||
|
||
val pendingIntent =
|
||
PendingIntent.getActivity(
|
||
this,
|
||
0,
|
||
intent,
|
||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||
)
|
||
|
||
val notification =
|
||
NotificationCompat.Builder(this, CHANNEL_ID)
|
||
.setSmallIcon(R.drawable.ic_notification)
|
||
.setContentTitle(title)
|
||
.setContentText(body)
|
||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||
.setAutoCancel(true)
|
||
.setContentIntent(pendingIntent)
|
||
.build()
|
||
|
||
val notificationManager =
|
||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
|
||
}
|
||
|
||
/** Создать notification channel для Android 8+ */
|
||
private fun createNotificationChannel() {
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||
val channel =
|
||
NotificationChannel(
|
||
CHANNEL_ID,
|
||
CHANNEL_NAME,
|
||
NotificationManager.IMPORTANCE_HIGH
|
||
)
|
||
.apply {
|
||
description = "Notifications for new messages"
|
||
enableVibration(true)
|
||
}
|
||
|
||
val notificationManager =
|
||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||
notificationManager.createNotificationChannel(channel)
|
||
}
|
||
}
|
||
|
||
/** Сохранить FCM токен в SharedPreferences */
|
||
private fun saveFcmToken(token: String) {
|
||
val prefs = getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE)
|
||
prefs.edit().putString("fcm_token", token).apply()
|
||
}
|
||
|
||
/** Проверка: замьючен ли диалог для текущего аккаунта */
|
||
private fun isDialogMuted(senderPublicKey: String): Boolean {
|
||
if (senderPublicKey.isBlank()) return false
|
||
return runCatching {
|
||
val accountManager = AccountManager(applicationContext)
|
||
val currentAccount = accountManager.getLastLoggedPublicKey().orEmpty()
|
||
runBlocking(Dispatchers.IO) {
|
||
PreferencesManager(applicationContext).isChatMuted(currentAccount, senderPublicKey)
|
||
}
|
||
}.getOrDefault(false)
|
||
}
|
||
}
|