package com.craigvg.lichun_android.managers import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import com.craigvg.lichun_android.BuildConfig import com.craigvg.lichun_android.MainActivity import com.craigvg.lichun_android.R import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import javax.inject.Singleton /** * Push notification manager for handling FCM tokens and notifications * * Note: Firebase dependencies are commented out in build.gradle. * When you add google-services.json and enable Firebase dependencies, * add FirebaseMessagingService implementation. * * Ported from iOS PushNotificationManager.swift */ @Singleton class PushNotificationManager( private val context: Context ) { private val notificationManager = NotificationManagerCompat.from(context) private val _fcmToken = MutableStateFlow(null) val fcmToken: StateFlow = _fcmToken.asStateFlow() private val _permissionGranted = MutableStateFlow(false) val permissionGranted: StateFlow = _permissionGranted.asStateFlow() companion object { private const val TAG = "PushNotificationManager" // Notification channels const val CHANNEL_EVENTS = "events" const val CHANNEL_MESSAGES = "messages" const val CHANNEL_ACHIEVEMENTS = "achievements" const val CHANNEL_REMINDERS = "reminders" // Notification IDs private const val NOTIFICATION_ID_EVENT = 1000 private const val NOTIFICATION_ID_MESSAGE = 2000 private const val NOTIFICATION_ID_ACHIEVEMENT = 3000 private const val NOTIFICATION_ID_REMINDER = 4000 } /** * Initialize the notification manager and create channels */ fun initialize() { createNotificationChannels() checkPermissionStatus() if (BuildConfig.FIREBASE_ENABLED) { try { com.google.firebase.messaging.FirebaseMessaging.getInstance().token .addOnCompleteListener { task -> if (task.isSuccessful) { _fcmToken.value = task.result Log.d(TAG, "FCM Token: ${task.result}") } else { Log.w(TAG, "Failed to get FCM token", task.exception) } } } catch (e: Exception) { Log.w(TAG, "Firebase not configured: ${e.message}") } } else { Log.d(TAG, "PushNotificationManager initialized (stub - Firebase not enabled)") } } /** * Create notification channels for Android O+ */ private fun createNotificationChannels() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channels = listOf( NotificationChannel( CHANNEL_EVENTS, "Life Events", NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "Notifications about life events in your game" }, NotificationChannel( CHANNEL_MESSAGES, "Messages", NotificationManager.IMPORTANCE_HIGH ).apply { description = "Messages from characters" }, NotificationChannel( CHANNEL_ACHIEVEMENTS, "Achievements", NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "Achievement unlocks and rewards" }, NotificationChannel( CHANNEL_REMINDERS, "Reminders", NotificationManager.IMPORTANCE_LOW ).apply { description = "Game reminders and updates" } ) val notificationManager = context.getSystemService(NotificationManager::class.java) channels.forEach { channel -> notificationManager.createNotificationChannel(channel) } Log.d(TAG, "Notification channels created") } } /** * Check if notification permission is granted */ fun checkPermissionStatus() { _permissionGranted.value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.checkSelfPermission( context, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED } else { true // Permission not required before Android 13 } } /** * Show a local notification for a life event */ fun showEventNotification(title: String, message: String, eventId: String? = null) { showNotification( channelId = CHANNEL_EVENTS, notificationId = NOTIFICATION_ID_EVENT + (eventId?.hashCode() ?: 0).mod(1000), title = title, message = message, data = eventId?.let { mapOf("eventId" to it) } ) } /** * Show a local notification for a message */ fun showMessageNotification( title: String, message: String, characterId: String? = null, characterName: String? = null ) { showNotification( channelId = CHANNEL_MESSAGES, notificationId = NOTIFICATION_ID_MESSAGE + (characterId?.hashCode() ?: 0).mod(1000), title = characterName ?: title, message = message, data = characterId?.let { mapOf("characterId" to it) } ) } /** * Show a local notification for an achievement */ fun showAchievementNotification(achievementName: String, reward: Int? = null) { val message = if (reward != null) { "You earned $reward diamonds!" } else { "You've unlocked a new achievement!" } showNotification( channelId = CHANNEL_ACHIEVEMENTS, notificationId = NOTIFICATION_ID_ACHIEVEMENT + achievementName.hashCode().mod(1000), title = "Achievement Unlocked!", message = "$achievementName - $message" ) } /** * Show a local notification for a reminder */ fun showReminderNotification(title: String, message: String) { showNotification( channelId = CHANNEL_REMINDERS, notificationId = NOTIFICATION_ID_REMINDER, title = title, message = message ) } /** * Generic method to show a notification */ private fun showNotification( channelId: String, notificationId: Int, title: String, message: String, data: Map? = null ) { if (!_permissionGranted.value) { Log.w(TAG, "Notification permission not granted") return } val intent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK data?.forEach { (key, value) -> putExtra(key, value) } } val pendingIntent = PendingIntent.getActivity( context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val notification = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(title) .setContentText(message) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .setAutoCancel(true) .build() try { notificationManager.notify(notificationId, notification) Log.d(TAG, "Notification shown: $title") } catch (e: SecurityException) { Log.e(TAG, "Failed to show notification: ${e.message}") } } /** * Cancel a specific notification */ fun cancelNotification(notificationId: Int) { notificationManager.cancel(notificationId) } /** * Cancel all notifications */ fun cancelAllNotifications() { notificationManager.cancelAll() } /** * Subscribe to a topic (for targeted notifications) */ fun subscribeToTopic(topic: String) { if (BuildConfig.FIREBASE_ENABLED) { try { com.google.firebase.messaging.FirebaseMessaging.getInstance().subscribeToTopic(topic) .addOnCompleteListener { task -> if (task.isSuccessful) { Log.d(TAG, "Subscribed to topic: $topic") } else { Log.w(TAG, "Failed to subscribe to topic: $topic", task.exception) } } } catch (e: Exception) { Log.w(TAG, "Firebase not configured: ${e.message}") } } else { Log.d(TAG, "Would subscribe to topic: $topic (stub)") } } /** * Unsubscribe from a topic */ fun unsubscribeFromTopic(topic: String) { if (BuildConfig.FIREBASE_ENABLED) { try { com.google.firebase.messaging.FirebaseMessaging.getInstance().unsubscribeFromTopic(topic) .addOnCompleteListener { task -> if (task.isSuccessful) { Log.d(TAG, "Unsubscribed from topic: $topic") } else { Log.w(TAG, "Failed to unsubscribe from topic: $topic", task.exception) } } } catch (e: Exception) { Log.w(TAG, "Firebase not configured: ${e.message}") } } else { Log.d(TAG, "Would unsubscribe from topic: $topic (stub)") } } /** Callback to send token to backend via WebSocket */ var onTokenRefresh: ((String) -> Unit)? = null /** * Update FCM token (called from FirebaseMessagingService) */ fun updateToken(token: String) { _fcmToken.value = token Log.d(TAG, "FCM Token updated: $token") onTokenRefresh?.invoke(token) } /** * Handle incoming push notification data */ fun handlePushData(data: Map) { val type = data["type"] ?: return when (type) { "event" -> { val title = data["title"] ?: "New Event" val message = data["message"] ?: "" val eventId = data["eventId"] showEventNotification(title, message, eventId) } "message" -> { val characterId = data["characterId"] val characterName = data["characterName"] val message = data["message"] ?: "You have a new message" showMessageNotification("New Message", message, characterId, characterName) } "achievement" -> { val achievementName = data["achievementName"] ?: "New Achievement" val reward = data["reward"]?.toIntOrNull() showAchievementNotification(achievementName, reward) } "reminder" -> { val title = data["title"] ?: "Reminder" val message = data["message"] ?: "Don't forget to check your game!" showReminderNotification(title, message) } } } }