- Integrated Firebase Cloud Messaging (FCM) for Android - Integrated Apple Push Notification Service (APNs) for iOS - Created shared notification models and API client - Added device registration and token management - Added notification permission handling for Android - Created PushNotificationManager for iOS with AppDelegate - Added placeholder google-services.json (needs to be replaced with actual config) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
5.6 KiB
Kotlin
149 lines
5.6 KiB
Kotlin
package com.example.mycrib
|
|
|
|
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 kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.launch
|
|
|
|
class MyFirebaseMessagingService : FirebaseMessagingService() {
|
|
|
|
override fun onNewToken(token: String) {
|
|
super.onNewToken(token)
|
|
Log.d(TAG, "New FCM token: $token")
|
|
|
|
// Store token locally for registration with backend
|
|
getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
.edit()
|
|
.putString(KEY_FCM_TOKEN, token)
|
|
.apply()
|
|
|
|
// Send token to backend API if user is logged in
|
|
// Note: In a real app, you might want to use WorkManager for reliable delivery
|
|
CoroutineScope(Dispatchers.IO).launch {
|
|
try {
|
|
val authToken = com.mycrib.storage.TokenStorage.getToken()
|
|
if (authToken != null) {
|
|
val notificationApi = com.mycrib.shared.network.NotificationApi()
|
|
val request = com.mycrib.shared.models.DeviceRegistrationRequest(
|
|
registrationId = token,
|
|
platform = "android"
|
|
)
|
|
|
|
when (val result = notificationApi.registerDevice(authToken, request)) {
|
|
is com.mycrib.shared.network.ApiResult.Success -> {
|
|
Log.d(TAG, "Device registered successfully with new token")
|
|
}
|
|
is com.mycrib.shared.network.ApiResult.Error -> {
|
|
Log.e(TAG, "Failed to register device with new token: ${result.message}")
|
|
}
|
|
is com.mycrib.shared.network.ApiResult.Loading,
|
|
is com.mycrib.shared.network.ApiResult.Idle -> {
|
|
// These states shouldn't occur for direct API calls
|
|
}
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Error registering device with new token", e)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onMessageReceived(message: RemoteMessage) {
|
|
super.onMessageReceived(message)
|
|
|
|
Log.d(TAG, "Message received from: ${message.from}")
|
|
|
|
// Check if message contains notification payload
|
|
message.notification?.let { notification ->
|
|
Log.d(TAG, "Notification: ${notification.title} - ${notification.body}")
|
|
sendNotification(
|
|
notification.title ?: "MyCrib",
|
|
notification.body ?: "",
|
|
message.data
|
|
)
|
|
}
|
|
|
|
// Check if message contains data payload
|
|
if (message.data.isNotEmpty()) {
|
|
Log.d(TAG, "Message data: ${message.data}")
|
|
|
|
// If there's no notification payload, create one from data
|
|
if (message.notification == null) {
|
|
val title = message.data["title"] ?: "MyCrib"
|
|
val body = message.data["body"] ?: ""
|
|
sendNotification(title, body, message.data)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun sendNotification(title: String, body: String, data: Map<String, String>) {
|
|
val intent = Intent(this, MainActivity::class.java).apply {
|
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
|
|
// Add data to intent for handling when notification is clicked
|
|
data.forEach { (key, value) ->
|
|
putExtra(key, value)
|
|
}
|
|
}
|
|
|
|
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
|
} else {
|
|
PendingIntent.FLAG_ONE_SHOT
|
|
}
|
|
|
|
val pendingIntent = PendingIntent.getActivity(
|
|
this,
|
|
0,
|
|
intent,
|
|
pendingIntentFlags
|
|
)
|
|
|
|
val channelId = getString(R.string.default_notification_channel_id)
|
|
val notificationBuilder = NotificationCompat.Builder(this, channelId)
|
|
.setSmallIcon(R.mipmap.ic_launcher)
|
|
.setContentTitle(title)
|
|
.setContentText(body)
|
|
.setAutoCancel(true)
|
|
.setContentIntent(pendingIntent)
|
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
|
|
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
|
|
// Create notification channel for Android O and above
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
val channel = NotificationChannel(
|
|
channelId,
|
|
"MyCrib Notifications",
|
|
NotificationManager.IMPORTANCE_HIGH
|
|
).apply {
|
|
description = "Notifications for tasks, residences, and warranties"
|
|
}
|
|
notificationManager.createNotificationChannel(channel)
|
|
}
|
|
|
|
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
|
}
|
|
|
|
companion object {
|
|
private const val TAG = "FCMService"
|
|
private const val NOTIFICATION_ID = 0
|
|
private const val PREFS_NAME = "mycrib_prefs"
|
|
private const val KEY_FCM_TOKEN = "fcm_token"
|
|
|
|
fun getStoredToken(context: Context): String? {
|
|
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
.getString(KEY_FCM_TOKEN, null)
|
|
}
|
|
}
|
|
}
|