Исправлены push-уведомления: восстановлена подписка токена и fallback обработки
This commit is contained in:
@@ -458,15 +458,24 @@ class MainActivity : FragmentActivity() {
|
|||||||
// Сохраняем токен локально
|
// Сохраняем токен локально
|
||||||
saveFcmToken(token)
|
saveFcmToken(token)
|
||||||
addFcmLog("💾 Токен сохранен локально")
|
addFcmLog("💾 Токен сохранен локально")
|
||||||
|
if (ProtocolManager.isAuthenticated()) {
|
||||||
// Token will be sent by ProtocolManager.onAuthenticated()
|
runCatching {
|
||||||
// when protocol reaches AUTHENTICATED state
|
ProtocolManager.subscribePushTokenIfAvailable(
|
||||||
|
forceToken = token
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onSuccess {
|
||||||
|
addFcmLog("🔔 Push token отправлен на сервер сразу")
|
||||||
|
}
|
||||||
|
.onFailure { error ->
|
||||||
|
addFcmLog(
|
||||||
|
"❌ Ошибка отправки push token: ${error.message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addFcmLog("⚠️ Токен пустой")
|
addFcmLog("⚠️ Токен пустой")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Токен будет отправлен через ProtocolManager.subscribePushTokenIfAvailable()
|
|
||||||
// при достижении состояния AUTHENTICATED
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
addFcmLog("❌ Ошибка Firebase: ${e.message}")
|
addFcmLog("❌ Ошибка Firebase: ${e.message}")
|
||||||
|
|||||||
@@ -187,11 +187,15 @@ object ProtocolManager {
|
|||||||
getProtocol().state.collect { newState ->
|
getProtocol().state.collect { newState ->
|
||||||
val previous = lastProtocolState
|
val previous = lastProtocolState
|
||||||
if (newState == ProtocolState.AUTHENTICATED && previous != ProtocolState.AUTHENTICATED) {
|
if (newState == ProtocolState.AUTHENTICATED && previous != ProtocolState.AUTHENTICATED) {
|
||||||
|
// New authenticated websocket session: always allow fresh push subscribe.
|
||||||
|
lastSubscribedToken = null
|
||||||
onAuthenticated()
|
onAuthenticated()
|
||||||
}
|
}
|
||||||
if (newState != ProtocolState.AUTHENTICATED && newState != ProtocolState.HANDSHAKING) {
|
if (newState != ProtocolState.AUTHENTICATED && newState != ProtocolState.HANDSHAKING) {
|
||||||
syncRequestInFlight = false
|
syncRequestInFlight = false
|
||||||
setSyncInProgress(false)
|
setSyncInProgress(false)
|
||||||
|
// Connection/session dropped: force re-subscribe on next AUTHENTICATED.
|
||||||
|
lastSubscribedToken = null
|
||||||
}
|
}
|
||||||
lastProtocolState = newState
|
lastProtocolState = newState
|
||||||
}
|
}
|
||||||
@@ -616,10 +620,6 @@ object ProtocolManager {
|
|||||||
* [com.rosetta.messenger.push.RosettaFirebaseMessagingService.onNewToken]
|
* [com.rosetta.messenger.push.RosettaFirebaseMessagingService.onNewToken]
|
||||||
* when Firebase rotates the token mid-session.
|
* when Firebase rotates the token mid-session.
|
||||||
*
|
*
|
||||||
* On each connect we send UNSUBSCRIBE first to clear any duplicate
|
|
||||||
* registrations that may have accumulated on the server, then SUBSCRIBE
|
|
||||||
* once — guaranteeing exactly one active push binding per device.
|
|
||||||
*
|
|
||||||
* @param forceToken if non-null, use this token instead of reading SharedPreferences
|
* @param forceToken if non-null, use this token instead of reading SharedPreferences
|
||||||
* (used by onNewToken which already has the fresh token).
|
* (used by onNewToken which already has the fresh token).
|
||||||
*/
|
*/
|
||||||
@@ -638,23 +638,13 @@ object ProtocolManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) UNSUBSCRIBE — clears ALL existing registrations for this token on the server.
|
|
||||||
// This removes duplicates that may have been created before the dedup fix.
|
|
||||||
val unsubPacket = PacketPushNotification().apply {
|
|
||||||
notificationsToken = token
|
|
||||||
action = PushNotificationAction.UNSUBSCRIBE
|
|
||||||
}
|
|
||||||
send(unsubPacket)
|
|
||||||
addLog("🔕 Push token UNSUBSCRIBE sent (clearing duplicates)")
|
|
||||||
|
|
||||||
// 2) SUBSCRIBE — register exactly once.
|
|
||||||
val subPacket = PacketPushNotification().apply {
|
val subPacket = PacketPushNotification().apply {
|
||||||
notificationsToken = token
|
notificationsToken = token
|
||||||
action = PushNotificationAction.SUBSCRIBE
|
action = PushNotificationAction.SUBSCRIBE
|
||||||
}
|
}
|
||||||
send(subPacket)
|
send(subPacket)
|
||||||
lastSubscribedToken = token
|
lastSubscribedToken = token
|
||||||
addLog("🔔 Push token SUBSCRIBE sent — single registration")
|
addLog("🔔 Push token SUBSCRIBE sent")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestSynchronize() {
|
private fun requestSynchronize() {
|
||||||
|
|||||||
@@ -132,6 +132,19 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
handledMessageData = true
|
handledMessageData = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val looksLikeMessagePayload =
|
||||||
|
type.contains("message") ||
|
||||||
|
data.keys.any { key ->
|
||||||
|
val lower = key.lowercase(Locale.ROOT)
|
||||||
|
lower.contains("message") ||
|
||||||
|
lower.contains("text") ||
|
||||||
|
lower.contains("body")
|
||||||
|
}
|
||||||
|
if (!handledMessageData && !isReadEvent && looksLikeMessagePayload) {
|
||||||
|
showSimpleNotification(senderName, messagePreview)
|
||||||
|
handledMessageData = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем notification payload (если есть).
|
// Обрабатываем notification payload (если есть).
|
||||||
@@ -164,11 +177,6 @@ class RosettaFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
}
|
}
|
||||||
lastNotifTimestamps[dedupKey] = now
|
lastNotifTimestamps[dedupKey] = now
|
||||||
Log.d(TAG, "\u2705 Showing notification for key=$dedupKey")
|
Log.d(TAG, "\u2705 Showing notification for key=$dedupKey")
|
||||||
// Desktop parity: suppress notifications during sync (useDialogFiber.ts checks
|
|
||||||
// protocolState != ProtocolState.SYNCHRONIZATION before calling notify()).
|
|
||||||
if (ProtocolManager.syncInProgress.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val senderKey = senderPublicKey?.trim().orEmpty()
|
val senderKey = senderPublicKey?.trim().orEmpty()
|
||||||
if (senderKey.isNotEmpty() && isDialogMuted(senderKey)) {
|
if (senderKey.isNotEmpty() && isDialogMuted(senderKey)) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -187,15 +187,9 @@ fun InAppCameraScreen(
|
|||||||
window.statusBarColor = originalStatusBarColor
|
window.statusBarColor = originalStatusBarColor
|
||||||
insetsController.isAppearanceLightStatusBars = originalLightStatusBars
|
insetsController.isAppearanceLightStatusBars = originalLightStatusBars
|
||||||
|
|
||||||
// Navigation bar: восстанавливаем только если есть нативные кнопки
|
|
||||||
if (com.rosetta.messenger.ui.utils.NavigationModeUtils.hasNativeNavigationBar(context)) {
|
|
||||||
window.navigationBarColor = originalNavigationBarColor
|
window.navigationBarColor = originalNavigationBarColor
|
||||||
insetsController.isAppearanceLightNavigationBars = originalLightNavigationBars
|
insetsController.isAppearanceLightNavigationBars = originalLightNavigationBars
|
||||||
} else {
|
insetsController.show(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
|
||||||
insetsController.hide(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insetsController.systemBarsBehavior =
|
|
||||||
androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,15 +173,10 @@ fun OnboardingScreen(
|
|||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
val window = (view.context as android.app.Activity).window
|
val window = (view.context as android.app.Activity).window
|
||||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||||
if (NavigationModeUtils.hasNativeNavigationBar(view.context)) {
|
|
||||||
window.navigationBarColor =
|
window.navigationBarColor =
|
||||||
if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
|
if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
|
||||||
} else {
|
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||||
// Жестовая навигация — прячем бар
|
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insetsController.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,25 +55,15 @@ object NavigationModeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Показывает или прячет navigation bar в зависимости от типа навигации.
|
* Показывает navigation bar на всех устройствах.
|
||||||
* - Кнопочная навигация → показываем бар
|
|
||||||
* - Жестовая навигация → прячем бар, свайп снизу временно покажет
|
|
||||||
*/
|
*/
|
||||||
fun applyNavigationBarVisibility(
|
fun applyNavigationBarVisibility(
|
||||||
insetsController: WindowInsetsControllerCompat,
|
insetsController: WindowInsetsControllerCompat,
|
||||||
context: Context,
|
context: Context,
|
||||||
isDarkTheme: Boolean
|
isDarkTheme: Boolean
|
||||||
) {
|
) {
|
||||||
if (hasNativeNavigationBar(context)) {
|
|
||||||
// Есть нативные кнопки — показываем навигационный бар
|
|
||||||
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||||
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
} else {
|
|
||||||
// Жестовая навигация — прячем навигационный бар
|
|
||||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insetsController.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.view.View
|
|||||||
import android.view.Window
|
import android.view.Window
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
|
|
||||||
data class SystemBarsState(
|
data class SystemBarsState(
|
||||||
val statusBarColor: Int,
|
val statusBarColor: Int,
|
||||||
@@ -48,17 +47,11 @@ object SystemBarsStyleUtils {
|
|||||||
if (window == null || view == null) return
|
if (window == null || view == null) return
|
||||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||||
|
|
||||||
if (NavigationModeUtils.hasNativeNavigationBar(context)) {
|
|
||||||
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
window.navigationBarColor = state.navigationBarColor
|
window.navigationBarColor = state.navigationBarColor
|
||||||
insetsController.isAppearanceLightNavigationBars = state.isLightNavigationBars
|
insetsController.isAppearanceLightNavigationBars = state.isLightNavigationBars
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insetsController.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreChatAfterFullscreen(
|
fun restoreChatAfterFullscreen(
|
||||||
|
|||||||
Reference in New Issue
Block a user