From 607c501aecfd65b93fbebdcc1688237819c7049f Mon Sep 17 00:00:00 2001 From: Yassine El graoui <164810676+yassine04e@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:33:57 +0100 Subject: [PATCH 1/2] Fix: Adding callbacks to notification view model (#271) * chore: preparing the branch in github to bhe able to pull * fix: Trying to 'solve' the incoherency problem * chore: Removing useless comment * fix: Putting the load in the onSuccess call of the sendNotification --- .../ui/notification/NotificationItemTest.kt | 3 +- .../ui/notification/NotificationScreenTest.kt | 4 +- .../travelpouch/ui/profile/ProfileEditTest.kt | 2 +- .../ui/travel/ParticipantListScreenTest.kt | 13 ++- .../com/github/se/travelpouch/MainActivity.kt | 2 +- .../notifications/NotificationRepository.kt | 6 +- .../NotificationRepositoryFirestore.kt | 12 +- .../notifications/NotificationViewModel.kt | 30 ++--- .../push/PushNotificationService.kt | 18 +-- .../model/profile/ProfileModelView.kt | 29 ++--- .../model/profile/ProfileRepository.kt | 31 ++--- .../profile/ProfileRepositoryFirebase.kt | 44 +++---- .../model/profile/ProfileRepositoryMock.kt | 18 +-- .../se/travelpouch/ui/home/TravelList.kt | 55 +++++---- .../ui/notifications/NotificationItem.kt | 110 +++++++++++------- .../ui/profile/ModifyingProfileScreen.kt | 28 +++-- .../ui/travel/ParticipantListScreen.kt | 38 +++--- ...NotificationRepositoryFirestoreUnitTest.kt | 4 +- .../NotificationViewModelUnitTest.kt | 6 +- 19 files changed, 254 insertions(+), 199 deletions(-) diff --git a/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationItemTest.kt b/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationItemTest.kt index e21efb10c..8a2dac86a 100644 --- a/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationItemTest.kt +++ b/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationItemTest.kt @@ -81,7 +81,7 @@ class NotificationItemTest { @Before fun setUp() { - FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext()) + FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext()) travelRepository = mock(TravelRepository::class.java) notificationRepository = mock(NotificationRepository::class.java) @@ -98,7 +98,6 @@ class NotificationItemTest { activityViewModel = ActivityViewModel(activityRepository) documentViewModel = DocumentViewModel(documentRepository, documentsManager, mock()) eventViewModel = EventViewModel(eventRepository) - } @Test diff --git a/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationScreenTest.kt b/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationScreenTest.kt index e43815823..1d22a34d5 100644 --- a/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationScreenTest.kt +++ b/app/src/androidTest/java/com/github/se/travelpouch/ui/notification/NotificationScreenTest.kt @@ -267,7 +267,7 @@ class NotificationScreenTest { composeTestRule.onNodeWithText("DECLINE").performClick() verify(profileRepository, never()).addFriend(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - verify(notificationRepository).addNotification(anyOrNull()) + verify(notificationRepository).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } // Chat-GPT was helpful in this test because he helped me understand how to separate the capture @@ -395,6 +395,6 @@ class NotificationScreenTest { verify(secondLayerTask).addOnSuccessListener(onCompleteListenerCaptor.capture()) composeTestRule.runOnIdle { onCompleteListenerCaptor.firstValue.onSuccess(null) } - verify(notificationRepository).addNotification(anyOrNull()) + verify(notificationRepository).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } } diff --git a/app/src/androidTest/java/com/github/se/travelpouch/ui/profile/ProfileEditTest.kt b/app/src/androidTest/java/com/github/se/travelpouch/ui/profile/ProfileEditTest.kt index 0292a35a5..ad5fbae3b 100644 --- a/app/src/androidTest/java/com/github/se/travelpouch/ui/profile/ProfileEditTest.kt +++ b/app/src/androidTest/java/com/github/se/travelpouch/ui/profile/ProfileEditTest.kt @@ -191,7 +191,7 @@ class ProfileEditTest { // piece of code is executed from the argument captor composeTestRule.runOnIdle { onCompleteListenerCaptor2.firstValue.onSuccess(mockQuerySnapshot) } - verify(notificationRepository).addNotification(anyOrNull()) + verify(notificationRepository).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } @Test diff --git a/app/src/androidTest/java/com/github/se/travelpouch/ui/travel/ParticipantListScreenTest.kt b/app/src/androidTest/java/com/github/se/travelpouch/ui/travel/ParticipantListScreenTest.kt index b0c0a8585..df0b4e749 100644 --- a/app/src/androidTest/java/com/github/se/travelpouch/ui/travel/ParticipantListScreenTest.kt +++ b/app/src/androidTest/java/com/github/se/travelpouch/ui/travel/ParticipantListScreenTest.kt @@ -422,7 +422,7 @@ class ParticipantListScreenTest { // Verify interactions with the repositories verify(profileRepository).getFsUidByEmail(anyOrNull(), anyOrNull(), anyOrNull()) - verify(notificationRepository, never()).addNotification(anyOrNull()) + verify(notificationRepository, never()).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } @Test @@ -471,7 +471,7 @@ class ParticipantListScreenTest { composeTestRule.onNodeWithTag("addUserButton").performClick() verify(profileRepository).getFsUidByEmail(anyOrNull(), anyOrNull(), anyOrNull()) - verify(notificationRepository, never()).addNotification(anyOrNull()) + verify(notificationRepository, never()).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } @Test @@ -550,7 +550,7 @@ class ParticipantListScreenTest { composeTestRule.onNodeWithTag("addUserButton").performClick() verify(profileRepository).getFsUidByEmail(anyOrNull(), anyOrNull(), anyOrNull()) - verify(notificationRepository, never()).addNotification(anyOrNull()) + verify(notificationRepository, never()).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } @Test @@ -672,7 +672,8 @@ class ParticipantListScreenTest { .updateTravel(any(), any(), anyOrNull(), any(), any(), anyOrNull()) composeTestRule.onNodeWithTag("addUserButton").performClick() verify(profileRepository).getFsUidByEmail(anyOrNull(), anyOrNull(), anyOrNull()) - verify(notificationRepository, atLeastOnce()).addNotification(anyOrNull()) + verify(notificationRepository, atLeastOnce()) + .addNotification(anyOrNull(), anyOrNull(), anyOrNull()) // throw impossible exception doAnswer { invocation -> val email = invocation.getArgument(0) @@ -697,7 +698,7 @@ class ParticipantListScreenTest { .updateTravel(any(), any(), anyOrNull(), any(), any(), anyOrNull()) doThrow(RuntimeException("Impossible Exception")) .`when`(notificationRepository) - .addNotification(anyOrNull()) + .addNotification(anyOrNull(), anyOrNull(), anyOrNull()) composeTestRule.onNodeWithTag("addUserFab").performClick() val randomEmail2 = "random.email@example.org" composeTestRule.onNodeWithTag("addUserEmailField").performScrollTo() @@ -787,7 +788,7 @@ class ParticipantListScreenTest { composeTestRule.onNodeWithTag("friendCard").assertIsDisplayed() composeTestRule.onNodeWithTag("friendCard").assertTextContains("example@mail.com") composeTestRule.onNodeWithTag("friendCard").performClick() - verify(notificationRepository).addNotification(anyOrNull()) + verify(notificationRepository).addNotification(anyOrNull(), anyOrNull(), anyOrNull()) } @Test diff --git a/app/src/main/java/com/github/se/travelpouch/MainActivity.kt b/app/src/main/java/com/github/se/travelpouch/MainActivity.kt index ffbad4a25..0ba15e521 100644 --- a/app/src/main/java/com/github/se/travelpouch/MainActivity.kt +++ b/app/src/main/java/com/github/se/travelpouch/MainActivity.kt @@ -69,7 +69,7 @@ class MainActivity : ComponentActivity() { } } } - FirebaseApp.initializeApp(this) + FirebaseApp.initializeApp(this) } @Composable diff --git a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepository.kt b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepository.kt index 93bc2cba9..a8decd623 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepository.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepository.kt @@ -5,7 +5,11 @@ interface NotificationRepository { fun getNewUid(): String - fun addNotification(notification: Notification) + fun addNotification( + notification: Notification, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) fun fetchNotificationsForUser( userId: String, diff --git a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestore.kt b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestore.kt index 35b380909..fb3e00aba 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestore.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestore.kt @@ -27,13 +27,21 @@ class NotificationRepositoryFirestore(private val firestore: FirebaseFirestore) * * @param notification The notification to be added. */ - override fun addNotification(notification: Notification) { + override fun addNotification( + notification: Notification, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { firestore .collection(FirebasePaths.notifications) .document(notification.notificationUid) .set(notification) - .addOnSuccessListener { Log.d("NotificationRepository", "Notification added successfully") } + .addOnSuccessListener { + onSuccess() + Log.d("NotificationRepository", "Notification added successfully") + } .addOnFailureListener { e -> + onFailure(Exception("An error occurred. Could not send the notification")) Log.e("NotificationRepository", "Error adding notification", e) } } diff --git a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationViewModel.kt b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationViewModel.kt index 779f45e9d..a57c9fe97 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationViewModel.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/notifications/NotificationViewModel.kt @@ -3,7 +3,6 @@ package com.github.se.travelpouch.model.notifications import android.util.Log import androidx.lifecycle.ViewModel -import com.google.firebase.Firebase import com.google.firebase.functions.FirebaseFunctions import com.google.firebase.functions.functions import dagger.hilt.android.lifecycle.HiltViewModel @@ -57,8 +56,12 @@ constructor(private val notificationRepository: NotificationRepository) : ViewMo * * @param notification The notification to be sent. */ - fun sendNotification(notification: Notification) { - notificationRepository.addNotification(notification) + fun sendNotification( + notification: Notification, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + notificationRepository.addNotification(notification, onSuccess, onFailure) } /** @@ -80,19 +83,16 @@ constructor(private val notificationRepository: NotificationRepository) : ViewMo } fun sendNotificationToUser(userId: String, notificationContent: NotificationContent) { - val data = hashMapOf( - "userId" to userId, - "message" to notificationContent.toDisplayString(), - ) + val data = + hashMapOf( + "userId" to userId, + "message" to notificationContent.toDisplayString(), + ) functions - .getHttpsCallable("sendNotification") - .call(data) - .addOnSuccessListener { result -> - Log.d("Notification", "Success: ${result.data}") - } - .addOnFailureListener { e -> - Log.e("Notification", "Error: ${e.message}") - } + .getHttpsCallable("sendNotification") + .call(data) + .addOnSuccessListener { result -> Log.d("Notification", "Success: ${result.data}") } + .addOnFailureListener { e -> Log.e("Notification", "Error: ${e.message}") } } } diff --git a/app/src/main/java/com/github/se/travelpouch/model/notifications/push/PushNotificationService.kt b/app/src/main/java/com/github/se/travelpouch/model/notifications/push/PushNotificationService.kt index eb0ff8db3..94b6f0282 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/notifications/push/PushNotificationService.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/notifications/push/PushNotificationService.kt @@ -5,13 +5,13 @@ import com.google.firebase.messaging.RemoteMessage class PushNotificationService : FirebaseMessagingService() { - @Override - override fun onNewToken(token: String) { - super.onNewToken(token) - } + @Override + override fun onNewToken(token: String) { + super.onNewToken(token) + } - @Override - override fun onMessageReceived(remoteMessage: RemoteMessage) { - super.onMessageReceived(remoteMessage) - } -} \ No newline at end of file + @Override + override fun onMessageReceived(remoteMessage: RemoteMessage) { + super.onMessageReceived(remoteMessage) + } +} diff --git a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileModelView.kt b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileModelView.kt index 7b05302e1..9098c7bf0 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileModelView.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileModelView.kt @@ -28,8 +28,9 @@ class ProfileModelView @Inject constructor(private val repository: ProfileReposi private val profile_ = MutableStateFlow(ErrorProfile.errorProfile) val profile: StateFlow = profile_.asStateFlow() - private var _isTokenUpdated = mutableStateOf(false) - val isTokenUpdated: Boolean get() = _isTokenUpdated.value + private var _isTokenUpdated = mutableStateOf(false) + val isTokenUpdated: Boolean + get() = _isTokenUpdated.value /** The initialisation function of the profile model view. It fetches the profile of the user */ suspend fun initAfterLogin(onSuccess: () -> Unit) { @@ -91,20 +92,20 @@ class ProfileModelView @Inject constructor(private val repository: ProfileReposi }) } - private fun addNotificationTokenToProfile(token: String) { - repository.addNotificationTokenToProfile(token, profile_.value.fsUid, { - Log.d("Notification token added", "Notification token added") - }, { - Log.e(onFailureTag, "Failed to add notification token", it) - }) - } + private fun addNotificationTokenToProfile(token: String) { + repository.addNotificationTokenToProfile( + token, + profile_.value.fsUid, + { Log.d("Notification token added", "Notification token added") }, + { Log.e(onFailureTag, "Failed to add notification token", it) }) + } - fun updateNotificationTokenIfNeeded(token: String) { - if (!_isTokenUpdated.value) { - addNotificationTokenToProfile(token) - _isTokenUpdated.value = true // Mark the token as updated for this session - } + fun updateNotificationTokenIfNeeded(token: String) { + if (!_isTokenUpdated.value) { + addNotificationTokenToProfile(token) + _isTokenUpdated.value = true // Mark the token as updated for this session } + } /** * This function sends to notification to add a friend diff --git a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepository.kt b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepository.kt index ad56c2e97..8a9eda385 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepository.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepository.kt @@ -57,21 +57,22 @@ interface ProfileRepository { onFailure: (Exception) -> Unit ) - /** - * Adds a notification token to the user's profile in the Firestore database. - * - * @param token The notification token to be added to the profile. - * @param user The user identifier to whom the token belongs. - * @param onSuccess A callback function that is invoked when the token is successfully added. - * @param onFailure A callback function that is invoked with an Exception if an error occurs during the operation. - */ - fun addNotificationTokenToProfile( - token: String, - user: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit - ) - + /** + * Adds a notification token to the user's profile in the Firestore database. + * + * @param token The notification token to be added to the profile. + * @param user The user identifier to whom the token belongs. + * @param onSuccess A callback function that is invoked when the token is successfully added. + * @param onFailure A callback function that is invoked with an Exception if an error occurs + * during the operation. + */ + fun addNotificationTokenToProfile( + token: String, + user: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) + /** * This function sends to notification to add a friend * diff --git a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryFirebase.kt b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryFirebase.kt index f3384ca95..fdb4f8b51 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryFirebase.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryFirebase.kt @@ -330,28 +330,28 @@ class ProfileRepositoryFirebase(private val db: FirebaseFirestore) : ProfileRepo } } - /** - * Adds a notification token to the user's profile in the Firestore database. - * - * @param token The notification token to be added to the profile. - * @param user The user identifier to whom the token belongs. - * @param onSuccess A callback function that is invoked when the token is successfully added. - * @param onFailure A callback function that is invoked with an Exception if an error occurs during the operation. - */ - override fun addNotificationTokenToProfile( - token: String, - user: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit - ) { - performFirestoreOperation( - db.collection(collectionPath) - .document(user) - .update("notificationTokens", FieldValue.arrayUnion(token)), - onSuccess, - onFailure - ) - } + /** + * Adds a notification token to the user's profile in the Firestore database. + * + * @param token The notification token to be added to the profile. + * @param user The user identifier to whom the token belongs. + * @param onSuccess A callback function that is invoked when the token is successfully added. + * @param onFailure A callback function that is invoked with an Exception if an error occurs + * during the operation. + */ + override fun addNotificationTokenToProfile( + token: String, + user: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + performFirestoreOperation( + db.collection(collectionPath) + .document(user) + .update("notificationTokens", FieldValue.arrayUnion(token)), + onSuccess, + onFailure) + } } /** This class is used to convert a document to a profile, across the project */ diff --git a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryMock.kt b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryMock.kt index 64473fb21..1a77f0367 100644 --- a/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryMock.kt +++ b/app/src/main/java/com/github/se/travelpouch/model/profile/ProfileRepositoryMock.kt @@ -57,15 +57,15 @@ class ProfileRepositoryMock : ProfileRepository { TODO("Not yet implemented") } - override fun addNotificationTokenToProfile( - token: String, - user: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit - ) { - TODO("Not yet implemented") - } - + override fun addNotificationTokenToProfile( + token: String, + user: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + TODO("Not yet implemented") + } + override fun sendFriendNotification( email: String, onSuccess: (String) -> Unit, diff --git a/app/src/main/java/com/github/se/travelpouch/ui/home/TravelList.kt b/app/src/main/java/com/github/se/travelpouch/ui/home/TravelList.kt index ade412da5..fd7596159 100644 --- a/app/src/main/java/com/github/se/travelpouch/ui/home/TravelList.kt +++ b/app/src/main/java/com/github/se/travelpouch/ui/home/TravelList.kt @@ -89,13 +89,13 @@ import com.github.se.travelpouch.model.travels.TravelContainer import com.github.se.travelpouch.ui.navigation.NavigationActions import com.github.se.travelpouch.ui.navigation.Screen import com.github.se.travelpouch.ui.navigation.TopLevelDestinations -import com.google.firebase.Firebase -import com.google.firebase.messaging.messaging import com.github.se.travelpouch.ui.theme.logoutIconDark import com.github.se.travelpouch.ui.theme.logoutIconLight import com.github.se.travelpouch.ui.theme.logoutRedDark import com.github.se.travelpouch.ui.theme.logoutRedLight +import com.google.firebase.Firebase import com.google.firebase.auth.auth +import com.google.firebase.messaging.messaging import java.util.Locale import kotlinx.coroutines.launch @@ -121,8 +121,8 @@ fun TravelListScreen( documentViewModel: DocumentViewModel, profileModelView: ProfileModelView ) { - // Ask for notification permission - RequestNotificationPermission(profileModelView) + // Ask for notification permission + RequestNotificationPermission(profileModelView) // Fetch travels when the screen is launched LaunchedEffect(Unit) { @@ -538,35 +538,32 @@ private fun resizeFromDragMotion( @Composable fun RequestNotificationPermission(profileViewModel: ProfileModelView) { - val context = LocalContext.current + val context = LocalContext.current - // Check notification permission - val hasPermission = remember { BuildConfig.DEBUG } || run { - Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || + // Check notification permission + val hasPermission = + remember { BuildConfig.DEBUG } || + run { + Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || ContextCompat.checkSelfPermission( - context, - android.Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } + context, android.Manifest.permission.POST_NOTIFICATIONS) == + PackageManager.PERMISSION_GRANTED + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPermission) { - ActivityCompat.requestPermissions( - context as Activity, - arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), - 0 - ) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPermission) { + ActivityCompat.requestPermissions( + context as Activity, arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 0) + } - // Only call Firestore logic if not already updated - if (hasPermission && !profileViewModel.isTokenUpdated) { - LaunchedEffect(Unit) { - Firebase.messaging.token.addOnCompleteListener { - if (it.isSuccessful) { - val token = it.result - profileViewModel.updateNotificationTokenIfNeeded(token) - } - } + // Only call Firestore logic if not already updated + if (hasPermission && !profileViewModel.isTokenUpdated) { + LaunchedEffect(Unit) { + Firebase.messaging.token.addOnCompleteListener { + if (it.isSuccessful) { + val token = it.result + profileViewModel.updateNotificationTokenIfNeeded(token) } + } } + } } - diff --git a/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt b/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt index 135375ad9..ac12f1da7 100644 --- a/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt +++ b/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt @@ -115,7 +115,7 @@ fun InvitationButtons( context: android.content.Context, eventsViewModel: EventViewModel ) { - var b by remember { mutableStateOf(true) } + var buttonPressed by remember { mutableStateOf(true) } Row( modifier = Modifier.fillMaxWidth().testTag("notification_item_buttons"), @@ -127,8 +127,8 @@ fun InvitationButtons( notificationViewModel, context, eventsViewModel, - { b = !b }, - b) + { buttonPressed = !buttonPressed }, + buttonPressed) DeclineButton( notification, listTravelViewModel, @@ -136,8 +136,8 @@ fun InvitationButtons( notificationViewModel, context, eventsViewModel, - { b = !b }, - b) + { buttonPressed = !buttonPressed }, + buttonPressed) } } @@ -225,11 +225,9 @@ fun handleInvitationResponse( if (isAccepted) NotificationType.ACCEPTED else NotificationType.DECLINED val responseMessage = if (isAccepted) "ACCEPTED" else "DECLINED" - - val responseNotification = - NotificationContent.InvitationResponseNotification( - profileViewModel.profile.value.username, - travel!!.title, isAccepted) + val responseNotification = + NotificationContent.InvitationResponseNotification( + profileViewModel.profile.value.username, travel!!.title, isAccepted) val invitationResponse = Notification( notification.notificationUid, @@ -240,29 +238,48 @@ fun handleInvitationResponse( responseType, sector = notification.sector) - notificationViewModel.sendNotificationToUser( - notification.senderUid, - responseNotification - ) - - - notificationViewModel.sendNotification(invitationResponse) if (isAccepted) { listTravelViewModel.addUserToTravel( profileViewModel.profile.value.email, travel, - { updatedContainer -> + onSuccess = { updatedContainer -> listTravelViewModel.selectTravel(updatedContainer) Toast.makeText(context, "User added successfully!", Toast.LENGTH_SHORT).show() + notificationViewModel.sendNotification( + invitationResponse, + onSuccess = { + Toast.makeText(context, responseMessage, Toast.LENGTH_SHORT).show() + + notificationViewModel.sendNotificationToUser( + notification.senderUid, responseNotification) + + notificationViewModel.loadNotificationsForUser( + profileViewModel.profile.value.fsUid) + }, + onFailure = { + Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show() + }) }, - { + onFailure = { Toast.makeText(context, "Failed to add user", Toast.LENGTH_SHORT).show() chosen() }, eventsViewModel.getNewDocumentReferenceForNewTravel(travel.fsUid)) + } else { + notificationViewModel.sendNotification( + invitationResponse, + onSuccess = { + Toast.makeText(context, responseMessage, Toast.LENGTH_SHORT).show() + notificationViewModel.sendNotificationToUser( + notification.senderUid, responseNotification) + notificationViewModel.loadNotificationsForUser( + profileViewModel.profile.value.fsUid) + }, + onFailure = { + Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show() + chosen() + }) } - notificationViewModel.loadNotificationsForUser(profileViewModel.profile.value.fsUid) - Toast.makeText(context, responseMessage, Toast.LENGTH_SHORT).show() }, onFailure = { Toast.makeText(context, "Failed to get travel", Toast.LENGTH_SHORT).show() @@ -274,9 +291,9 @@ fun handleInvitationResponse( profileViewModel.addFriend( notification.senderUid, onSuccess = { - val firendNotification = - NotificationContent.FriendInvitationResponseNotification( - profileViewModel.profile.value.email, true) + val firendNotification = + NotificationContent.FriendInvitationResponseNotification( + profileViewModel.profile.value.email, true) val invitationResponse = Notification( notification.notificationUid, @@ -287,19 +304,26 @@ fun handleInvitationResponse( NotificationType.ACCEPTED, sector = notification.sector) - notificationViewModel.sendNotification(invitationResponse) - notificationViewModel.sendNotificationToUser( - notification.senderUid, - firendNotification - ) + notificationViewModel.sendNotification( + invitationResponse, + onSuccess = { + notificationViewModel.sendNotificationToUser( + notification.senderUid, firendNotification) + notificationViewModel.loadNotificationsForUser( + profileViewModel.profile.value.fsUid) + }, + onFailure = { Toast.makeText(context, it.message, Toast.LENGTH_LONG).show() }) Toast.makeText(context, "Friend added", Toast.LENGTH_LONG).show() }, - onFailure = { e -> Toast.makeText(context, e.message!!, Toast.LENGTH_LONG).show() }) + onFailure = { e -> + Toast.makeText(context, e.message!!, Toast.LENGTH_LONG).show() + chosen() + }) } else { - val firendNotification = - NotificationContent.FriendInvitationResponseNotification( - profileViewModel.profile.value.email, false) + val firendNotification = + NotificationContent.FriendInvitationResponseNotification( + profileViewModel.profile.value.email, false) val invitationResponse = Notification( notification.notificationUid, @@ -310,14 +334,20 @@ fun handleInvitationResponse( NotificationType.DECLINED, sector = notification.sector) - notificationViewModel.sendNotification(invitationResponse) + notificationViewModel.sendNotification( + invitationResponse, + onSuccess = { + notificationViewModel.sendNotificationToUser( + notification.senderUid, firendNotification) + + Toast.makeText(context, "Request declined", Toast.LENGTH_LONG).show() - notificationViewModel.sendNotificationToUser( - notification.senderUid, - firendNotification - ) - - Toast.makeText(context, "Request declined", Toast.LENGTH_LONG).show() + notificationViewModel.loadNotificationsForUser(profileViewModel.profile.value.fsUid) + }, + onFailure = { + chosen() + Toast.makeText(context, it.message, Toast.LENGTH_LONG).show() + }) } } } diff --git a/app/src/main/java/com/github/se/travelpouch/ui/profile/ModifyingProfileScreen.kt b/app/src/main/java/com/github/se/travelpouch/ui/profile/ModifyingProfileScreen.kt index d46da87e5..ca57f9e3d 100644 --- a/app/src/main/java/com/github/se/travelpouch/ui/profile/ModifyingProfileScreen.kt +++ b/app/src/main/java/com/github/se/travelpouch/ui/profile/ModifyingProfileScreen.kt @@ -252,11 +252,9 @@ fun ModifyingProfileScreen( profileModelView.sendFriendNotification( email = friendMail, onSuccess = { friendUid -> - Toast.makeText(context, "Invitation sent", Toast.LENGTH_LONG) - .show() - val notificationContent = - NotificationContent.FriendInvitationNotification( - profile.value.email) + val notificationContent = + NotificationContent.FriendInvitationNotification( + profile.value.email) notificationViewModel.sendNotification( Notification( notificationViewModel.getNewUid(), @@ -266,10 +264,22 @@ fun ModifyingProfileScreen( notificationContent, notificationType = NotificationType.INVITATION, status = NotificationStatus.UNREAD, - sector = NotificationSector.PROFILE)) - notificationViewModel.sendNotificationToUser( - friendUid, - notificationContent) + sector = NotificationSector.PROFILE), + onSuccess = { + notificationViewModel.sendNotificationToUser( + friendUid, notificationContent) + + Toast.makeText( + context, "Invitation sent", Toast.LENGTH_LONG) + .show() + }, + onFailure = { + Toast.makeText( + context, + "Failed to send invitation", + Toast.LENGTH_LONG) + .show() + }) openDialog = false }, diff --git a/app/src/main/java/com/github/se/travelpouch/ui/travel/ParticipantListScreen.kt b/app/src/main/java/com/github/se/travelpouch/ui/travel/ParticipantListScreen.kt index e7717d0b6..4be4e8457 100644 --- a/app/src/main/java/com/github/se/travelpouch/ui/travel/ParticipantListScreen.kt +++ b/app/src/main/java/com/github/se/travelpouch/ui/travel/ParticipantListScreen.kt @@ -381,11 +381,9 @@ private fun inviteUserToTravelViaFsuid( Toast.makeText(context, "Error: User already added", Toast.LENGTH_SHORT).show() } else if (fsUid != null) { try { - val invitationNotification = - NotificationContent.InvitationNotification( - profileViewModel.profile.value.name, - selectedTravel.title, - Role.PARTICIPANT) + val invitationNotification = + NotificationContent.InvitationNotification( + profileViewModel.profile.value.name, selectedTravel.title, Role.PARTICIPANT) notificationViewModel.sendNotification( Notification( notificationViewModel.getNewUid(), @@ -394,10 +392,14 @@ private fun inviteUserToTravelViaFsuid( selectedTravel!!.fsUid, invitationNotification, notificationType = NotificationType.INVITATION, - sector = NotificationSector.TRAVEL)) - - notificationViewModel.sendNotificationToUser(fsUid, invitationNotification) - Toast.makeText(context, "Invitation sent", Toast.LENGTH_SHORT).show() + sector = NotificationSector.TRAVEL), + onSuccess = { + notificationViewModel.sendNotificationToUser(fsUid, invitationNotification) + Toast.makeText(context, "Invitation sent", Toast.LENGTH_SHORT).show() + }, + onFailure = { + Toast.makeText(context, "Failed to send invitation", Toast.LENGTH_SHORT).show() + }) } catch (e: Exception) { Log.e("NotificationError", "Failed to send notification: ${e.message}") } @@ -437,10 +439,8 @@ fun handleRoleChange( if (oldRole == Role.OWNER) { // Actual role change logic if (participant.key != profileViewModel.profile.value.fsUid) { - val roleChangeNotification = - NotificationContent.RoleChangeNotification( - selectedTravel.title, - newRole) + val roleChangeNotification = + NotificationContent.RoleChangeNotification(selectedTravel.title, newRole) notificationViewModel.sendNotification( Notification( notificationViewModel.getNewUid(), @@ -449,11 +449,13 @@ fun handleRoleChange( selectedTravel.fsUid, roleChangeNotification, NotificationType.ROLE_UPDATE, - sector = NotificationSector.TRAVEL)) - notificationViewModel.sendNotificationToUser( - participant.key, - roleChangeNotification - ) + sector = NotificationSector.TRAVEL), + onSuccess = { + notificationViewModel.sendNotificationToUser(participant.key, roleChangeNotification) + }, + onFailure = { + Toast.makeText(context, "Failed to send invitation", Toast.LENGTH_SHORT).show() + }) } val participantMap = selectedTravel.allParticipants.toMutableMap() participantMap[Participant(participant.key)] = newRole diff --git a/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestoreUnitTest.kt b/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestoreUnitTest.kt index bdfc64060..94ec23526 100644 --- a/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestoreUnitTest.kt +++ b/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationRepositoryFirestoreUnitTest.kt @@ -94,7 +94,7 @@ class NotificationRepositoryFirestoreUnitTest { .thenReturn(documentReference) `when`(documentReference.set(any())).thenReturn(task) - notificationRepositoryFirestore.addNotification(notification) + notificationRepositoryFirestore.addNotification(notification, {}, {}) verify(documentReference).set(any()) } @@ -121,7 +121,7 @@ class NotificationRepositoryFirestoreUnitTest { } // Act - notificationRepositoryFirestore.addNotification(notification) + notificationRepositoryFirestore.addNotification(notification, {}, {}) // Wait for the latch to be released or timeout latch.await(2, TimeUnit.SECONDS) diff --git a/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationViewModelUnitTest.kt b/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationViewModelUnitTest.kt index db7de4de5..213bfc086 100644 --- a/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationViewModelUnitTest.kt +++ b/app/src/test/java/com/github/se/travelpouch/model/notifications/NotificationViewModelUnitTest.kt @@ -26,6 +26,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.robolectric.RobolectricTestRunner @@ -131,8 +132,9 @@ class NotificationViewModelUnitTest { "John Doe", "Trip to Paris", Role.PARTICIPANT), notificationType = NotificationType.INVITATION, sector = sector) - notificationViewModel.sendNotification(notification) - verify(notificationRepositoryFirestore, times(1)).addNotification(eq(notification)) + notificationViewModel.sendNotification(notification, {}, {}) + verify(notificationRepositoryFirestore, times(1)) + .addNotification(eq(notification), anyOrNull(), anyOrNull()) } @Test From 6ec56e39c453e7ad487c401d75470aa26326181a Mon Sep 17 00:00:00 2001 From: Yassine El graoui <164810676+yassine04e@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:32:28 +0100 Subject: [PATCH 2/2] Hotfix: This fixes the invitation buttons when accepting or declining an invitation notification (#276) fix: This fixes the invitation buttons when accepting or declining an invitation notification --- .../github/se/travelpouch/ui/notifications/NotificationItem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt b/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt index ac12f1da7..42003f879 100644 --- a/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt +++ b/app/src/main/java/com/github/se/travelpouch/ui/notifications/NotificationItem.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -116,6 +117,7 @@ fun InvitationButtons( eventsViewModel: EventViewModel ) { var buttonPressed by remember { mutableStateOf(true) } + LaunchedEffect(notification.notificationUid) { buttonPressed = true } Row( modifier = Modifier.fillMaxWidth().testTag("notification_item_buttons"),