feat: Integrate Firebase Cloud Messaging for push notifications; add service to handle token and message reception

This commit is contained in:
k1ngsterr1
2026-01-16 23:06:41 +05:00
parent 7750f450e8
commit 431e3755c6
14 changed files with 1317 additions and 234 deletions

View File

@@ -0,0 +1,210 @@
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 android.util.Log
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.crypto.CryptoManager
import com.rosetta.messenger.data.AccountManager
import com.rosetta.messenger.network.ProtocolManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
/**
* 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"
private const val NOTIFICATION_ID = 1
}
/**
* Вызывается когда получен новый FCM токен
* Отправляем его на сервер через протокол
*/
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d(TAG, "🔔 New FCM token (short): ${token.take(20)}...")
Log.d(TAG, "🔔 New FCM token (FULL): $token")
// Сохраняем токен локально
saveFcmToken(token)
// TODO: Отправляем токен на сервер если аккаунт уже залогинен
/*
serviceScope.launch {
try {
val accountManager = AccountManager(applicationContext)
val currentAccount = accountManager.getCurrentAccount()
if (currentAccount != null) {
Log.d(TAG, "📤 Sending FCM token to server for account: ${currentAccount.publicKey.take(10)}...")
// Отправляем через протокол
val packet = PacketPushToken().apply {
this.privateKey = CryptoManager.generatePrivateKeyHash(currentAccount.privateKey)
this.publicKey = currentAccount.publicKey
this.pushToken = token
this.platform = "android"
}
ProtocolManager.send(packet)
Log.d(TAG, "✅ FCM token sent to server")
}
} catch (e: Exception) {
Log.e(TAG, "❌ Error sending FCM token to server", e)
}
}
*/
}
/**
* Вызывается когда получено push-уведомление
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d(TAG, "📬 Push notification received from: ${remoteMessage.from}")
// Обрабатываем data payload
remoteMessage.data.isNotEmpty().let {
Log.d(TAG, "📦 Message data payload: ${remoteMessage.data}")
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 если приложение открыто
Log.d(TAG, "📖 Message read by $senderPublicKey")
}
else -> {
Log.d(TAG, "⚠️ Unknown notification type: $type")
}
}
}
// Обрабатываем notification payload (если есть)
remoteMessage.notification?.let {
Log.d(TAG, "📨 Message Notification Body: ${it.body}")
showSimpleNotification(it.title ?: "Rosetta", it.body ?: "New message")
}
}
/**
* Показать уведомление о новом сообщении
*/
private fun showMessageNotification(senderPublicKey: String?, senderName: String, messagePreview: String) {
createNotificationChannel()
// 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,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.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(NOTIFICATION_ID, notification)
}
/**
* Показать простое уведомление
*/
private fun showSimpleNotification(title: String, body: String) {
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_launcher_foreground)
.setContentTitle(title)
.setContentText(body)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, 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()
Log.d(TAG, "💾 FCM token saved locally")
}
}