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) } }