diff --git a/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepository.kt b/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepository.kt index 84e759bbf5..b6a35f86fe 100644 --- a/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepository.kt +++ b/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepository.kt @@ -1,5 +1,11 @@ package org.ole.planet.myplanet.repository +data class NotificationData( + val type: String, + val message: String, + val relatedId: String? +) + interface NotificationRepository { suspend fun getUnreadCount(userId: String?): Int suspend fun updateResourceNotification(userId: String?, resourceCount: Int) @@ -11,4 +17,9 @@ interface NotificationRepository { relatedId: String?, userId: String?, ) + suspend fun createNotificationsBatch( + notifications: List, + userId: String? + ) + suspend fun cleanupDuplicateNotifications(userId: String?) } diff --git a/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepositoryImpl.kt b/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepositoryImpl.kt index 3016f1487e..b533787416 100644 --- a/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepositoryImpl.kt +++ b/app/src/main/java/org/ole/planet/myplanet/repository/NotificationRepositoryImpl.kt @@ -50,34 +50,95 @@ class NotificationRepositoryImpl @Inject constructor( }.toInt() } + override suspend fun createNotificationsBatch( + notifications: List, + userId: String? + ) { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] createNotificationsBatch called: ${notifications.size} notifications for user $userId") + if (notifications.isEmpty() || userId == null) return + + val actualUserId = userId + + // Pre-fetch all existing notifications for this user in a single query + val existingNotifications = withRealmAsync { realm -> + realm.where(RealmNotification::class.java) + .equalTo("userId", actualUserId) + .findAll() + .map { notif -> + // Create a unique key for fast lookup + val key = "${notif.type}:${notif.relatedId ?: "null"}" + key to notif + } + .toMap() + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Found ${existingNotifications.size} existing notifications in DB: ${existingNotifications.keys}") + + // Filter out notifications that already exist + val notificationsToCreate = notifications.filter { notif -> + val key = "${notif.type}:${notif.relatedId ?: "null"}" + !existingNotifications.containsKey(key) + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] After filtering: ${notificationsToCreate.size} new notifications to create") + + // Create notifications in smaller batches for better performance + if (notificationsToCreate.isNotEmpty()) { + val chunkSize = 10 + val chunks = notificationsToCreate.chunked(chunkSize) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Split into ${chunks.size} chunks of max $chunkSize") + + chunks.forEachIndexed { index, chunk -> + executeTransaction { realm -> + chunk.forEach { notif -> + realm.createObject(RealmNotification::class.java, UUID.randomUUID().toString()).apply { + this.userId = actualUserId + this.type = notif.type + this.message = notif.message + this.relatedId = notif.relatedId + this.createdAt = Date() + } + } + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Created chunk ${index + 1}/${chunks.size} (${chunk.size} notifications)") + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Created ${notificationsToCreate.size} notifications in DB") + } else { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] No new notifications to create") + } + } + override suspend fun updateResourceNotification(userId: String?, resourceCount: Int) { userId ?: return val notificationId = "$userId:resource:count" - val existingNotification = findByField(RealmNotification::class.java, "id", notificationId) - if (resourceCount > 0) { - val previousCount = existingNotification?.message?.toIntOrNull() ?: 0 - val countChanged = previousCount != resourceCount + executeTransaction { realm -> + val existingNotification = realm.where(RealmNotification::class.java) + .equalTo("id", notificationId) + .findFirst() - val notification = existingNotification?.apply { - message = "$resourceCount" - relatedId = "$resourceCount" - if (countChanged) { - this.isRead = false - this.createdAt = Date() + if (resourceCount > 0) { + val previousCount = existingNotification?.message?.toIntOrNull() ?: 0 + val countChanged = previousCount != resourceCount + + if (existingNotification != null) { + existingNotification.message = "$resourceCount" + existingNotification.relatedId = "$resourceCount" + if (countChanged) { + existingNotification.isRead = false + existingNotification.createdAt = Date() + } + } else { + realm.createObject(RealmNotification::class.java, notificationId).apply { + this.userId = userId + this.type = "resource" + this.message = "$resourceCount" + this.relatedId = "$resourceCount" + this.createdAt = Date() + } } - } ?: RealmNotification().apply { - this.id = notificationId - this.userId = userId - this.type = "resource" - this.message = "$resourceCount" - this.relatedId = "$resourceCount" - this.createdAt = Date() + } else { + existingNotification?.deleteFromRealm() } - save(notification) - } else { - existingNotification?.let { delete(RealmNotification::class.java, "id", it.id) } } } @@ -115,5 +176,41 @@ class NotificationRepositoryImpl @Inject constructor( } return updatedIds } + + override suspend fun cleanupDuplicateNotifications(userId: String?) { + if (userId == null) return + + // Get all notifications for this user + val allNotifications = withRealmAsync { realm -> + realm.where(RealmNotification::class.java) + .equalTo("userId", userId) + .findAll() + .let { realm.copyFromRealm(it) } + } + + // Group by type+relatedId, keep only the most recent one + val notificationsToKeep = allNotifications + .groupBy { "${it.type}:${it.relatedId ?: "null"}" } + .mapValues { (_, notifications) -> + // Keep the most recent one (latest createdAt) + notifications.maxByOrNull { it.createdAt?.time ?: 0L } + } + .values + .filterNotNull() + + val idsToKeep = notificationsToKeep.map { it.id }.toSet() + val idsToDelete = allNotifications.map { it.id }.filter { !idsToKeep.contains(it) } + + if (idsToDelete.isNotEmpty()) { + executeTransaction { realm -> + idsToDelete.forEach { idToDelete -> + realm.where(RealmNotification::class.java) + .equalTo("id", idToDelete) + .findFirst() + ?.deleteFromRealm() + } + } + } + } } diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt index 711105b7bb..39468bbb95 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt @@ -112,9 +112,12 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N private lateinit var challengeHelper: ChallengeHelper private lateinit var notificationManager: NotificationUtils.NotificationManager private var notificationsShownThisSession = false + private var initialNotificationCheckComplete = false private var lastNotificationCheckTime = 0L private val notificationCheckThrottleMs = 5000L private var systemNotificationReceiver: BroadcastReceiver? = null + @Volatile + private var isCreatingNotifications = false private interface RealmListener { fun removeListener() @@ -492,10 +495,11 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N private inline fun setupListener(crossinline query: () -> RealmResults) { val results = query() val listener = RealmChangeListener> { _ -> - if (notificationsShownThisSession) { + if (notificationsShownThisSession && initialNotificationCheckComplete) { val currentTime = System.currentTimeMillis() if (currentTime - lastNotificationCheckTime > notificationCheckThrottleMs) { lastNotificationCheckTime = currentTime + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Realm listener triggered notification check") checkAndCreateNewNotifications() } } @@ -557,57 +561,106 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N private fun checkIfShouldShowNotifications() { val fromLogin = intent.getBooleanExtra("from_login", false) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] checkIfShouldShowNotifications: fromLogin=$fromLogin, notificationsShownThisSession=$notificationsShownThisSession") if (fromLogin || !notificationsShownThisSession) { notificationsShownThisSession = true + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Will check notifications after 1s delay") lifecycleScope.launch { kotlinx.coroutines.delay(1000) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] 1s delay complete, calling checkAndCreateNewNotifications") checkAndCreateNewNotifications() } + } else { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Skipping notification check (already shown this session)") } } private fun checkAndCreateNewNotifications() { val userId = user?.id + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] checkAndCreateNewNotifications called for user: $userId") - lifecycleScope.launch(Dispatchers.IO) { - var unreadCount = 0 - val newNotifications = mutableListOf() + // Prevent duplicate concurrent executions + if (isCreatingNotifications) { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Already creating notifications, returning") + return + } - try { - dashboardViewModel.updateResourceNotification(userId) - databaseService.realmInstance.use { backgroundRealm -> - val createdNotifications = createNotifications(backgroundRealm, userId) - newNotifications.addAll(createdNotifications) + isCreatingNotifications = true + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Starting notification creation") - unreadCount = dashboardViewModel.getUnreadNotificationsSize(userId) - } - } catch (e: Exception) { - e.printStackTrace() - } + lifecycleScope.launch(Dispatchers.IO) { + try { + var unreadCount = 0 + val newNotifications = mutableListOf() - withContext(Dispatchers.Main) { try { - updateNotificationBadge(unreadCount) { - openNotificationsList(userId ?: "") + // Run slow operations in background AFTER showing notifications + lifecycleScope.launch(Dispatchers.IO) { + try { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Starting background resource notification update") + dashboardViewModel.updateResourceNotification(userId) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Background resource notification update complete") + } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error in background resource update", e) + } } - val groupedNotifications = newNotifications.groupBy { it.type } - - groupedNotifications.forEach { (type, notifications) -> - when { - notifications.size == 1 -> { - notificationManager.showNotification(notifications.first()) - } - notifications.size > 1 -> { - val summaryConfig = createSummaryNotification(type, notifications.size) - notificationManager.showNotification(summaryConfig) - } + // Run cleanup in background (don't block notification display) + lifecycleScope.launch(Dispatchers.IO) { + try { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Starting background cleanup of duplicate notifications") + dashboardViewModel.cleanupDuplicateNotifications(userId) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Background cleanup complete") + } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error in background cleanup", e) } } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Creating notifications from DB") + databaseService.realmInstance.use { backgroundRealm -> + val createdNotifications = createNotifications(backgroundRealm, userId) + newNotifications.addAll(createdNotifications) + unreadCount = dashboardViewModel.getUnreadNotificationsSize(userId) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Created ${createdNotifications.size} notification configs, unreadCount=$unreadCount") + } } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error creating notifications", e) e.printStackTrace() } + + withContext(Dispatchers.Main) { + try { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Updating badge and showing notifications") + updateNotificationBadge(unreadCount) { + openNotificationsList(userId ?: "") + } + + val groupedNotifications = newNotifications.groupBy { it.type } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Grouped notifications: ${groupedNotifications.mapValues { it.value.size }}") + + groupedNotifications.forEach { (type, notifications) -> + when { + notifications.size == 1 -> { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Showing single notification: type=$type, id=${notifications.first().id}") + notificationManager.showNotification(notifications.first()) + } + notifications.size > 1 -> { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Showing summary notification: type=$type, count=${notifications.size}") + val summaryConfig = createSummaryNotification(type, notifications.size) + notificationManager.showNotification(summaryConfig) + } + } + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Finished showing all notifications") + } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error showing notifications", e) + e.printStackTrace() + } + } + } finally { + isCreatingNotifications = false + initialNotificationCheckComplete = true + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] checkAndCreateNewNotifications complete, Realm listeners now active") } } } @@ -698,25 +751,78 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N userId: String?, ): List { val newNotifications = mutableListOf() - createSurveyDatabaseNotifications(realm, userId) - createTaskDatabaseNotifications(realm, userId) - createStorageDatabaseNotifications(realm, userId) - createJoinRequestDatabaseNotifications(realm, userId) - val unreadNotifications = realm.where(RealmNotification::class.java) - .equalTo("userId", userId) - .equalTo("isRead", false) - .findAll() + // Collect all notification data from Realm + val notificationsToCreate = mutableListOf() + + notificationsToCreate.addAll(collectSurveyNotifications(realm, userId)) + notificationsToCreate.addAll(collectTaskNotifications(realm, userId)) + notificationsToCreate.addAll(collectStorageNotifications(realm, userId)) + notificationsToCreate.addAll(collectJoinRequestNotifications(realm, userId)) + + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Collected ${notificationsToCreate.size} notifications from Realm queries") - unreadNotifications.forEach { dbNotification -> - val config = createNotificationConfigFromDatabase(dbNotification) + // Create notification configs directly from collected data (for immediate display) + notificationsToCreate.forEach { notifData -> + val config = createNotificationConfigFromData(notifData) if (config != null) { newNotifications.add(config) } } + + // Persist to database in background (don't block UI) + lifecycleScope.launch(Dispatchers.IO) { + try { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Starting DB persist in background") + dashboardViewModel.createNotificationsBatch(notificationsToCreate, userId) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] DB persist complete") + } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error persisting to DB", e) + } + } + return newNotifications } + private fun createNotificationConfigFromData(notifData: org.ole.planet.myplanet.repository.NotificationData): NotificationUtils.NotificationConfig? { + return when (notifData.type.lowercase()) { + "survey" -> notificationManager.createSurveyNotification( + notifData.relatedId ?: notifData.message, + notifData.message + ).copy( + extras = mapOf("surveyId" to (notifData.relatedId ?: notifData.message)) + ) + "task" -> { + val parts = notifData.message.split(" ") + val taskTitle = parts.dropLast(3).joinToString(" ") + val deadline = parts.takeLast(3).joinToString(" ") + notificationManager.createTaskNotification( + notifData.relatedId ?: notifData.message, + taskTitle, + deadline + ).copy( + extras = mapOf("taskId" to (notifData.relatedId ?: notifData.message)) + ) + } + "resource" -> notificationManager.createResourceNotification( + notifData.relatedId ?: "resource_notification", + notifData.message.toIntOrNull() ?: 0 + ) + "storage" -> { + val storageValue = notifData.message.replace("%", "").toIntOrNull() ?: 0 + notificationManager.createStorageWarningNotification(storageValue, notifData.relatedId ?: "storage") + } + "join_request" -> notificationManager.createJoinRequestNotification( + notifData.relatedId ?: "join_request", + "New Request", + notifData.message + ).copy( + extras = mapOf("requestId" to (notifData.relatedId ?: ""), "teamName" to notifData.message) + ) + else -> null + } + } + private fun createNotificationConfigFromDatabase(dbNotification: RealmNotification): NotificationUtils.NotificationConfig? { return when (dbNotification.type.lowercase()) { "survey" -> notificationManager.createSurveyNotification( @@ -752,51 +858,59 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N } } - private suspend fun createSurveyDatabaseNotifications(realm: Realm, userId: String?) { + private fun collectSurveyNotifications(realm: Realm, userId: String?): List { val pendingSurveys = realm.where(RealmSubmission::class.java) .equalTo("userId", userId) .equalTo("status", "pending") .equalTo("type", "survey") .findAll() - pendingSurveys.mapNotNull { submission -> + // Get unique survey titles (deduplicate multiple submissions for same survey) + val uniqueSurveyTitles = pendingSurveys.mapNotNull { submission -> val examId = submission.parentId?.split("@")?.firstOrNull() ?: "" realm.where(RealmStepExam::class.java) .equalTo("id", examId) .findFirst() ?.name - }.forEach { title -> - dashboardViewModel.createNotificationIfMissing("survey", title, title, userId) + }.distinct() + + return uniqueSurveyTitles.map { title -> + org.ole.planet.myplanet.repository.NotificationData("survey", title, title) } } - private suspend fun createTaskDatabaseNotifications(realm: Realm, userId: String?) { + private fun collectTaskNotifications(realm: Realm, userId: String?): List { val tasks = realm.where(RealmTeamTask::class.java) .notEqualTo("status", "archived") .equalTo("completed", false) .equalTo("assignee", userId) .findAll() - tasks.forEach { task -> - dashboardViewModel.createNotificationIfMissing( + return tasks.map { task -> + org.ole.planet.myplanet.repository.NotificationData( "task", "${task.title} ${formatDate(task.deadline)}", - task.id, - userId + task.id ) } } - private suspend fun createStorageDatabaseNotifications(realm: Realm, userId: String?) { + private fun collectStorageNotifications(realm: Realm, userId: String?): List { + val notifications = mutableListOf() + val storageRatio = FileUtils.totalAvailableMemoryRatio(this) if (storageRatio > 85) { - dashboardViewModel.createNotificationIfMissing("storage", "$storageRatio%", "storage", userId) + notifications.add(org.ole.planet.myplanet.repository.NotificationData("storage", "$storageRatio%", "storage")) } - dashboardViewModel.createNotificationIfMissing("storage", "90%", "storage_test", userId) + notifications.add(org.ole.planet.myplanet.repository.NotificationData("storage", "90%", "storage_test")) + + return notifications } - private suspend fun createJoinRequestDatabaseNotifications(realm: Realm, userId: String?) { + private fun collectJoinRequestNotifications(realm: Realm, userId: String?): List { + val allJoinRequests = mutableListOf>() // requestId, requesterUserId, teamId + val teamLeaderMemberships = realm.where(RealmMyTeam::class.java) .equalTo("userId", userId) .equalTo("docType", "membership") @@ -810,26 +924,39 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N .findAll() pendingJoinRequests.forEach { joinRequest -> - val team = realm.where(RealmMyTeam::class.java) - .equalTo("_id", leadership.teamId) - .findFirst() - - val requester = realm.where(RealmUserModel::class.java) - .equalTo("id", joinRequest.userId) - .findFirst() - - val requesterName = requester?.name ?: "Unknown User" - val teamName = team?.name ?: "Unknown Team" - val message = getString(R.string.user_requested_to_join_team, requesterName, teamName) - - dashboardViewModel.createNotificationIfMissing( - "join_request", - message, - joinRequest._id, - userId - ) + val requestId = joinRequest._id ?: return@forEach + val requesterUserId = joinRequest.userId ?: return@forEach + val teamId = leadership.teamId ?: return@forEach + + allJoinRequests.add(Triple(requestId, requesterUserId, teamId)) } } + + // Group by requester+team to find duplicates + val uniqueRequests = allJoinRequests + .groupBy { (_, requesterUserId, teamId) -> "$requesterUserId:$teamId" } + .mapValues { (_, requests) -> requests.first() } // Keep only the first request per user+team combo + .values + + return uniqueRequests.map { (requestId, requesterUserId, teamId) -> + val team = realm.where(RealmMyTeam::class.java) + .equalTo("_id", teamId) + .findFirst() + + val requester = realm.where(RealmUserModel::class.java) + .equalTo("id", requesterUserId) + .findFirst() + + val requesterName = requester?.name ?: "Unknown User" + val teamName = team?.name ?: "Unknown Team" + val message = getString(R.string.user_requested_to_join_team, requesterName, teamName) + + org.ole.planet.myplanet.repository.NotificationData( + "join_request", + message, + requestId + ) + } } private fun openNotificationsList(userId: String) { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardViewModel.kt index 8cdb5feea3..5844a810a3 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardViewModel.kt @@ -54,6 +54,13 @@ class DashboardViewModel @Inject constructor( notificationRepository.createNotificationIfMissing(type, message, relatedId, userId) } + suspend fun createNotificationsBatch( + notifications: List, + userId: String? + ) { + notificationRepository.createNotificationsBatch(notifications, userId) + } + suspend fun getPendingSurveys(userId: String?): List { return submissionRepository.getPendingSurveys(userId) } @@ -65,4 +72,8 @@ class DashboardViewModel @Inject constructor( suspend fun getUnreadNotificationsSize(userId: String?): Int { return notificationRepository.getUnreadCount(userId) } + + suspend fun cleanupDuplicateNotifications(userId: String?) { + notificationRepository.cleanupDuplicateNotifications(userId) + } } diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt b/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt index 47e9fffc05..e797658011 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt @@ -213,12 +213,16 @@ abstract class DashboardElementActivity : SyncActivity(), FragmentManager.OnBack fun logout() { lifecycleScope.launch { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Logout started") profileDbHandler.logoutAsync() SecurePrefs.clearCredentials(this@DashboardElementActivity) settings.edit { putBoolean(Constants.KEY_LOGIN, false) } settings.edit { putBoolean(Constants.KEY_NOTIFICATION_SHOWN, false) } NotificationUtils.cancelAll(this@DashboardElementActivity) - + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Clearing notification caches") + NotificationUtils.clearAllCaches(this@DashboardElementActivity) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Notification caches cleared, starting login screen") + val loginScreen = Intent(this@DashboardElementActivity, LoginActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra("fromLogout", true) diff --git a/app/src/main/java/org/ole/planet/myplanet/utilities/NotificationUtils.kt b/app/src/main/java/org/ole/planet/myplanet/utilities/NotificationUtils.kt index b37782dba8..4fed468817 100644 --- a/app/src/main/java/org/ole/planet/myplanet/utilities/NotificationUtils.kt +++ b/app/src/main/java/org/ole/planet/myplanet/utilities/NotificationUtils.kt @@ -89,6 +89,11 @@ object NotificationUtils { nm.cancelAll() } + @JvmStatic + fun clearAllCaches(context: Context) { + getInstance(context).clearAllNotificationCaches() + } + @JvmStatic fun setChannel(notificationManager: android.app.NotificationManager?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -158,19 +163,24 @@ object NotificationUtils { } fun showNotification(config: NotificationConfig): Boolean { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] showNotification called for: ${config.id}, type: ${config.type}") + if (!canShowNotification(config.type)) { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] canShowNotification=false for type: ${config.type}") return false } if (sessionShownNotifications.contains(config.id)) { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Already in sessionShownNotifications: ${config.id}") return false } val notificationId = config.id.hashCode() val activeNotifications = notificationManager.activeNotifications val isAlreadyShowing = activeNotifications.any { it.id == notificationId } - + if (isAlreadyShowing) { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Already showing in system: ${config.id}") return false } @@ -178,8 +188,10 @@ object NotificationUtils { val notification = buildNotification(config) notificationManager.notify(notificationId, notification) markNotificationAsShown(config.id) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Notification shown successfully: ${config.id}") return true } catch (e: Exception) { + android.util.Log.e("NotifTiming", "[${System.currentTimeMillis()}] Error showing notification: ${config.id}", e) e.printStackTrace() return false } @@ -378,12 +390,23 @@ object NotificationUtils { private fun loadActiveNotifications() { val saved = preferences.getStringSet(KEY_ACTIVE_NOTIFICATIONS, emptySet()) ?: emptySet() activeNotifications.addAll(saved) + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] Loaded ${saved.size} active notifications from cache: $saved") } private fun saveActiveNotifications() { preferences.edit { putStringSet(KEY_ACTIVE_NOTIFICATIONS, activeNotifications) } } + fun clearAllNotificationCaches() { + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] clearAllNotificationCaches called. Before: active=${activeNotifications.size}, session=${sessionShownNotifications.size}") + activeNotifications.clear() + sessionShownNotifications.clear() + preferences.edit { + remove(KEY_ACTIVE_NOTIFICATIONS) + } + android.util.Log.d("NotifTiming", "[${System.currentTimeMillis()}] clearAllNotificationCaches complete. After: active=${activeNotifications.size}, session=${sessionShownNotifications.size}") + } + fun createSurveyNotification(surveyId: String, surveyTitle: String): NotificationConfig { return NotificationConfig( id = surveyId,