From ea1a7e9c992322aedf87d3851e1bc6a8b5f1a976 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 01:36:04 +0100 Subject: [PATCH 01/22] fix: stop association icons flicker Saving/Unsaving event was requesting all of its organisers every time, resulting in the icons flickering. Putting lazy to true makes it request only when there is a change --- .../unio/model/event/EventViewModel.kt | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index 809ec0603..b2e08cd2c 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -177,17 +177,21 @@ constructor( fun updateEventWithoutImage(event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) { repository.addEvent(event, onSuccess, onFailure) - event.organisers.requestAll({ - event.organisers.list.value.forEach { - if (it.events.contains(event.uid)) it.events.remove(event.uid) - it.events.add(event.uid) - associationRepository.saveAssociation( - it, - {}, - { e -> Log.e("EventViewModel", "An error occurred while loading associations: $e") }) - it.events.requestAll() - } - }) + event.organisers.requestAll( + { + event.organisers.list.value.forEach { + if (it.events.contains(event.uid)) it.events.remove(event.uid) + it.events.add(event.uid) + associationRepository.saveAssociation( + it, + {}, + { e -> + Log.e("EventViewModel", "An error occurred while loading associations: $e") + }) + it.events.requestAll() + } + }, + lazy = true) _events.value = _events.value.filter { it.uid != event.uid } // Remove the outdated event _events.value += event @@ -209,6 +213,7 @@ constructor( }, onFailure = { exception -> Log.e("EventViewModel", "An error occurred while deleting event: $exception") + onFailure(exception) }) event.organisers.requestAll({ From 179bc1a9dce188ecc2df568ea37ab183311e07fe Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 02:22:02 +0100 Subject: [PATCH 02/22] fix: fetch organisers fetch organisers of the events of an association when selecting it --- .../android/unio/model/association/AssociationViewModel.kt | 7 ++++++- .../main/java/com/android/unio/ui/event/EventDetails.kt | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt b/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt index 2b8d93e00..c7157599c 100644 --- a/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt +++ b/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt @@ -14,6 +14,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.forEach /** * ViewModel class that manages the association list data and provides it to the UI. It exposes a @@ -201,7 +202,11 @@ constructor( fun selectAssociation(associationId: String) { _selectedAssociation.value = findAssociationById(associationId).also { it -> - it?.events?.requestAll() + it?.events?.requestAll( + { + it.events.list.value.forEach { event -> event.organisers.requestAll(lazy = true) } + }, + lazy = true) it?.members?.forEach { fetchUserFromMember(it) } } } diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index e0b2544a6..7344ffbbf 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -312,11 +312,12 @@ fun EventInformationCard(event: Event, organisers: List, context: C context.getString(R.string.event_association_icon_description), modifier = Modifier.size(ASSOCIATION_ICON_SIZE) - .clip(CircleShape) + .clip(RoundedCornerShape(5.dp)) .align(Alignment.CenterVertically) .testTag("${EventDetailsTestTags.ASSOCIATION_LOGO}$i"), placeholderResourceId = R.drawable.adec, - filterQuality = FilterQuality.None) + filterQuality = FilterQuality.None, + contentScale = ContentScale.Crop) Text( organisers[i].name, From 7049036316a313fc9a64c270f8d7daab11b3eba2 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 03:06:41 +0100 Subject: [PATCH 03/22] feat: change image filter quality --- app/src/main/java/com/android/unio/ui/event/EventDetails.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index 7344ffbbf..e82be9b24 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -431,7 +431,7 @@ fun EventDetailsPicturesTab(event: Event, context: Context) { item.image.toUri(), contentDescription = context.getString(R.string.event_details_user_picture_content_description), - filterQuality = FilterQuality.High, + filterQuality = FilterQuality.Medium, placeholderResourceId = 0, contentScale = ContentScale.Crop, modifier = From d7417934b8e8d0fb4b08ab74a4cfed55af629361 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 16:23:28 +0100 Subject: [PATCH 04/22] feat: create concurrent repository for Event and User, as well as implement getEventRef function --- .../unio/model/event/EventRepository.kt | 4 ++ .../model/event/EventRepositoryFirestore.kt | 11 ++++ .../save/ConcurrentEventUserRepository.kt | 11 ++++ .../ConcurrentEventUserRepositoryFirestore.kt | 55 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepository.kt create mode 100644 app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt diff --git a/app/src/main/java/com/android/unio/model/event/EventRepository.kt b/app/src/main/java/com/android/unio/model/event/EventRepository.kt index a6fc064d1..549af02a3 100644 --- a/app/src/main/java/com/android/unio/model/event/EventRepository.kt +++ b/app/src/main/java/com/android/unio/model/event/EventRepository.kt @@ -1,10 +1,14 @@ package com.android.unio.model.event +import com.google.firebase.firestore.DocumentReference + interface EventRepository { fun init(onSuccess: () -> Unit) fun getEventWithId(id: String, onSuccess: (Event) -> Unit, onFailure: (Exception) -> Unit) + fun getEventRef(uid: String): DocumentReference + fun getEvents(onSuccess: (List) -> Unit, onFailure: (Exception) -> Unit) fun getNewUid(): String diff --git a/app/src/main/java/com/android/unio/model/event/EventRepositoryFirestore.kt b/app/src/main/java/com/android/unio/model/event/EventRepositoryFirestore.kt index 98e1120e5..d5769890f 100644 --- a/app/src/main/java/com/android/unio/model/event/EventRepositoryFirestore.kt +++ b/app/src/main/java/com/android/unio/model/event/EventRepositoryFirestore.kt @@ -8,6 +8,7 @@ import com.android.unio.model.firestore.transform.hydrate import com.android.unio.model.firestore.transform.serialize import com.google.firebase.Firebase import com.google.firebase.auth.auth +import com.google.firebase.firestore.DocumentReference import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.MetadataChanges import javax.inject.Inject @@ -50,6 +51,16 @@ class EventRepositoryFirestore @Inject constructor(private val db: FirebaseFires } } + /** + * Fetches a [DocumentReference] for an [Event] object using the provided [uid]. + * + * @param uid [String] : The uid of the [Event] to fetch. + * @return [DocumentReference] : The [DocumentReference] for the [Event] object. + */ + override fun getEventRef(uid: String): DocumentReference { + return db.collection(EVENT_PATH).document(uid) + } + /** * Fetches all events from Firestore and calls the onSuccess callback with the list of events. * diff --git a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepository.kt b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepository.kt new file mode 100644 index 000000000..26884f44e --- /dev/null +++ b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepository.kt @@ -0,0 +1,11 @@ +package com.android.unio.model.save + +import com.android.unio.model.event.Event +import com.android.unio.model.user.User + +interface ConcurrentEventUserRepository { + + fun init(onSuccess: () -> Unit) + + fun updateSave(user: User, event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) +} diff --git a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt new file mode 100644 index 000000000..dc2e3874f --- /dev/null +++ b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt @@ -0,0 +1,55 @@ +package com.android.unio.model.save + +import com.android.unio.model.association.AssociationRepositoryFirestore +import com.android.unio.model.authentication.registerAuthStateListener +import com.android.unio.model.event.Event +import com.android.unio.model.event.EventRepository +import com.android.unio.model.firestore.transform.serialize +import com.android.unio.model.user.User +import com.android.unio.model.user.UserRepository +import com.android.unio.model.user.UserRepositoryFirestore +import com.google.firebase.Firebase +import com.google.firebase.auth.auth +import com.google.firebase.firestore.FirebaseFirestore +import javax.inject.Inject + +/** + * A Firestore implementation of [ConcurrentEventUserRepository]. This class is responsible for + * updating the Firestore database with the user's save status for an event. + * + * @property db The Firestore database. + * @property userRepository The repository for user data. + * @property eventRepository The repository for event data. + */ +class ConcurrentEventUserRepositoryFirestore +@Inject +constructor( + private val db: FirebaseFirestore, + private val userRepository: UserRepository, + private val eventRepository: EventRepository +) : ConcurrentEventUserRepository { + override fun init(onSuccess: () -> Unit) { + Firebase.auth.registerAuthStateListener { + if (it.currentUser != null) { + onSuccess() + } + } + } + + override fun updateSave( + user: User, + event: Event, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit + ) { + db.runBatch { batch -> + val userRef = userRepository.getUserRef(user.uid) + val associationRef = eventRepository.getEventRef(association.uid) + + batch.set(associationRef, AssociationRepositoryFirestore.serialize(association)) + batch.set(userRef, UserRepositoryFirestore.serialize(user)) + } + .addOnSuccessListener { onSuccess() } + .addOnFailureListener { onFailure(it) } + } +} From 52602350af635608d12597ba59160d071e08c250 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 16:25:46 +0100 Subject: [PATCH 05/22] feat: implement updateSave method --- .../ConcurrentEventUserRepositoryFirestore.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt index dc2e3874f..baea82e60 100644 --- a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt +++ b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt @@ -1,9 +1,9 @@ package com.android.unio.model.save -import com.android.unio.model.association.AssociationRepositoryFirestore import com.android.unio.model.authentication.registerAuthStateListener import com.android.unio.model.event.Event import com.android.unio.model.event.EventRepository +import com.android.unio.model.event.EventRepositoryFirestore import com.android.unio.model.firestore.transform.serialize import com.android.unio.model.user.User import com.android.unio.model.user.UserRepository @@ -28,6 +28,7 @@ constructor( private val userRepository: UserRepository, private val eventRepository: EventRepository ) : ConcurrentEventUserRepository { + override fun init(onSuccess: () -> Unit) { Firebase.auth.registerAuthStateListener { if (it.currentUser != null) { @@ -36,6 +37,15 @@ constructor( } } + /** + * Updates the Firestore database with the user's save status for an event. This operation is + * performed atomically. If the operation fails, the database is not updated. + * + * @param user The user. + * @param event The event. + * @param onSuccess The callback that is called when the operation is successful. + * @param onFailure The callback that is called when the operation fails. + */ override fun updateSave( user: User, event: Event, @@ -44,9 +54,9 @@ constructor( ) { db.runBatch { batch -> val userRef = userRepository.getUserRef(user.uid) - val associationRef = eventRepository.getEventRef(association.uid) + val eventRef = eventRepository.getEventRef(event.uid) - batch.set(associationRef, AssociationRepositoryFirestore.serialize(association)) + batch.set(eventRef, EventRepositoryFirestore.serialize(event)) batch.set(userRef, UserRepositoryFirestore.serialize(user)) } .addOnSuccessListener { onSuccess() } From 050ad14736f118f65a6bf13a31ca4be767b387e0 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 16:36:20 +0100 Subject: [PATCH 06/22] feat: implement updateSave in ViewModel --- .../unio/model/event/EventViewModel.kt | 45 +++++++++++++++++++ .../ConcurrentEventUserRepositoryFirestore.kt | 1 + 2 files changed, 46 insertions(+) diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index b2e08cd2c..47fdf979d 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -4,7 +4,11 @@ import android.util.Log import androidx.lifecycle.ViewModel import com.android.unio.model.association.AssociationRepository import com.android.unio.model.image.ImageRepository +import com.android.unio.model.save.ConcurrentEventUserRepository import com.android.unio.model.strings.StoragePathsStrings +import com.android.unio.model.user.User +import com.google.firebase.Firebase +import com.google.firebase.messaging.messaging import dagger.hilt.android.lifecycle.HiltViewModel import java.io.InputStream import javax.inject.Inject @@ -29,6 +33,7 @@ constructor( private val imageRepository: ImageRepository, private val associationRepository: AssociationRepository, private val eventUserPictureRepository: EventUserPictureRepository, + private val concurrentEventUserRepository: ConcurrentEventUserRepository ) : ViewModel() { /** @@ -263,4 +268,44 @@ constructor( }, onFailure = { e -> Log.e("ImageRepository", "Failed to store image: $e") }) } + + /** + * Updates the save status of the user for the target event. If the user has already saved the + * event, the event's interested count is decremented and the event is removed from the user's + * saved events. If the user is has not yet saved the event, the event's interested count is + * incremented and the event is added to the user's saved events. + * + * @param target The association to update the follow status for. + * @param user The user to update the follow status for. + * @param isUnsaveAction A boolean indicating whether the user is unfollowing the association. + * @param updateUser A callback to update the user in the repository. + */ + fun updateSave(target: Event, user: User, isUnsaveAction: Boolean, updateUser: () -> Unit) { + val updatedEvent: Event + val updatedUser: User = user.copy() + if (isUnsaveAction) { + val updatedSavedCount = if (target.numberOfSaved - 1 >= 0) target.numberOfSaved - 1 else 0 + updatedEvent = target.copy(numberOfSaved = updatedSavedCount) + updatedUser.followedAssociations.remove(target.uid) + Firebase.messaging.unsubscribeFromTopic(target.uid) + } else { + updatedEvent = target.copy(numberOfSaved = target.numberOfSaved + 1) + updatedUser.savedEvents.add(target.uid) + Firebase.messaging.subscribeToTopic(target.uid) + } + concurrentEventUserRepository.updateSave( + updatedUser, + updatedEvent, + { + _events.value = + _events.value.map { + if (it.uid == target.uid) { + updatedEvent + } else it + } + _selectedEvent.value = updatedEvent + updateUser() + }, + { exception -> Log.e("EventViewModel", "Failed to update save", exception) }) + } } diff --git a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt index baea82e60..64020fa0e 100644 --- a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt +++ b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt @@ -52,6 +52,7 @@ constructor( onSuccess: () -> Unit, onFailure: (Exception) -> Unit ) { + // TODO: test method db.runBatch { batch -> val userRef = userRepository.getUserRef(user.uid) val eventRef = eventRepository.getEventRef(event.uid) From e13df8eb3ce19c4644200f7c1423dcb3afe23d56 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 16:52:12 +0100 Subject: [PATCH 07/22] feat: refactor save logic --- .../com/android/unio/ui/event/EventDetails.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index e82be9b24..dcb9d2d3a 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -84,6 +84,7 @@ import com.android.unio.model.strings.FormatStrings.HOUR_MINUTE_FORMAT import com.android.unio.model.strings.NotificationStrings.EVENT_REMINDER_CHANNEL_ID import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags import com.android.unio.model.user.UserViewModel +import com.android.unio.model.utils.NetworkUtils import com.android.unio.ui.components.NotificationSender import com.android.unio.ui.image.AsyncImageWrapper import com.android.unio.ui.navigation.NavigationAction @@ -609,8 +610,33 @@ fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: } } } - + var enableButton by remember { mutableStateOf(true) } + val isConnected = NetworkUtils.checkInternetConnection(context) val onClickSaveButton = { + if (isConnected) { + enableButton = false + eventViewModel.updateSave(event, user!!, isSaved) { + userViewModel.refreshUser() + if (isSaved) { + if (notificationPermissionsEnabled) { + NotificationWorker.unschedule(context, event.uid.hashCode()) + } + } else { + if (!notificationPermissionsEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // this permission requires api 33 + // We should check how to make notifications work with lower api versions + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } else { + scheduleReminderNotification() + } + } + enableButton = true + isSaved = !isSaved + } + } + if (isSaved) { val newEvent = event.copy(numberOfSaved = event.numberOfSaved - 1) eventViewModel.updateEventWithoutImage( From 09f2279c43ab496beaa7beaf662aa70cabc42222 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 18:25:42 +0100 Subject: [PATCH 08/22] fix: save button works again --- .../unio/model/event/EventViewModel.kt | 11 ++-- .../unio/model/hilt/module/HiltModule.kt | 12 ++++ .../com/android/unio/ui/event/EventCard.kt | 5 +- .../com/android/unio/ui/event/EventDetails.kt | 66 +++++-------------- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index 47fdf979d..076baeb19 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -275,24 +275,27 @@ constructor( * saved events. If the user is has not yet saved the event, the event's interested count is * incremented and the event is added to the user's saved events. * - * @param target The association to update the follow status for. - * @param user The user to update the follow status for. - * @param isUnsaveAction A boolean indicating whether the user is unfollowing the association. + * @param target The event to update the saved status for. + * @param user The user to update the saved status for. + * @param isUnsaveAction A boolean indicating whether the user is unsave the event. * @param updateUser A callback to update the user in the repository. */ fun updateSave(target: Event, user: User, isUnsaveAction: Boolean, updateUser: () -> Unit) { val updatedEvent: Event val updatedUser: User = user.copy() + if (isUnsaveAction) { val updatedSavedCount = if (target.numberOfSaved - 1 >= 0) target.numberOfSaved - 1 else 0 updatedEvent = target.copy(numberOfSaved = updatedSavedCount) - updatedUser.followedAssociations.remove(target.uid) + updatedUser.savedEvents.remove(target.uid) Firebase.messaging.unsubscribeFromTopic(target.uid) } else { updatedEvent = target.copy(numberOfSaved = target.numberOfSaved + 1) updatedUser.savedEvents.add(target.uid) Firebase.messaging.subscribeToTopic(target.uid) } + println("target ${target.numberOfSaved}") + println(updatedEvent.numberOfSaved) concurrentEventUserRepository.updateSave( updatedUser, updatedEvent, diff --git a/app/src/main/java/com/android/unio/model/hilt/module/HiltModule.kt b/app/src/main/java/com/android/unio/model/hilt/module/HiltModule.kt index d02ebe968..83dce00d2 100644 --- a/app/src/main/java/com/android/unio/model/hilt/module/HiltModule.kt +++ b/app/src/main/java/com/android/unio/model/hilt/module/HiltModule.kt @@ -14,6 +14,8 @@ import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.map.LocationRepository import com.android.unio.model.map.nominatim.NominatimApiService import com.android.unio.model.map.nominatim.NominatimLocationRepository +import com.android.unio.model.save.ConcurrentEventUserRepository +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.user.UserRepository import com.android.unio.model.user.UserRepositoryFirestore import com.google.android.gms.location.FusedLocationProviderClient @@ -111,6 +113,16 @@ abstract class ConcurrentAssociationUserModule { ): ConcurrentAssociationUserRepository } +@Module +@InstallIn(SingletonComponent::class) +abstract class ConcurrentEventUserModule { + + @Binds + abstract fun bindConcurrentEventUserRepository( + concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore + ): ConcurrentEventUserRepository +} + @Module @InstallIn(SingletonComponent::class) object FirebaseStorageModule { diff --git a/app/src/main/java/com/android/unio/ui/event/EventCard.kt b/app/src/main/java/com/android/unio/ui/event/EventCard.kt index f066cde0f..9b1e70005 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventCard.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventCard.kt @@ -124,6 +124,9 @@ fun EventCardScaffold( userViewModel: UserViewModel ) { val context = LocalContext.current + + val events by eventViewModel.events.collectAsState() + Column( modifier = Modifier.fillMaxWidth() @@ -156,7 +159,7 @@ fun EventCardScaffold( .clip(RoundedCornerShape(4.dp)) .background(MaterialTheme.colorScheme.surfaceContainer)) { Text( - " ${event.numberOfSaved} " + + " ${events.first{it.uid == event.uid}.numberOfSaved} " + context.getString(R.string.event_card_interested_string) + " ", color = MaterialTheme.colorScheme.onSecondaryContainer) diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index dcb9d2d3a..03156b93e 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -91,9 +91,8 @@ import com.android.unio.ui.navigation.NavigationAction import com.android.unio.ui.navigation.Screen import com.android.unio.ui.navigation.SmoothTopBarNavigationMenu import com.android.unio.ui.theme.AppTypography -import com.google.firebase.Firebase +import com.android.unio.ui.utils.ToastUtils import com.google.firebase.Timestamp -import com.google.firebase.messaging.messaging import java.text.SimpleDateFormat import java.util.Locale import kotlinx.coroutines.CoroutineScope @@ -160,7 +159,7 @@ fun EventScreen( fun EventScreenScaffold( navigationAction: NavigationAction, mapViewModel: MapViewModel, - event: Event, + event: Event?, organisers: List, eventViewModel: EventViewModel, userViewModel: UserViewModel @@ -178,7 +177,7 @@ fun EventScreenScaffold( Scaffold( floatingActionButton = { if (pagerState.currentPage == 1) { - EventDetailsPicturePicker(event, eventViewModel, user!!) // Asserted non null above + EventDetailsPicturePicker(event!!, eventViewModel, user!!) // Asserted non null above } }, modifier = Modifier.testTag(EventDetailsTestTags.SCREEN), @@ -209,7 +208,7 @@ fun EventScreenScaffold( } }, actions = { - EventSaveButton(event, eventViewModel, userViewModel) + EventSaveButton(event!!, eventViewModel, userViewModel) IconButton( modifier = Modifier.testTag(EventDetailsTestTags.SHARE_BUTTON), onClick = { showSheet = true }) { @@ -221,13 +220,13 @@ fun EventScreenScaffold( }) }, content = { - EventScreenContent(navigationAction, mapViewModel, event, organisers, pagerState, tabList) + EventScreenContent(navigationAction, mapViewModel, event!!, organisers, pagerState, tabList) }) NotificationSender( dialogTitle = context.getString(R.string.event_send_notification), notificationType = NotificationType.EVENT_SAVERS, - topic = event.uid, + topic = event!!.uid, notificationContent = { mapOf("title" to event.title, "body" to it) }, showNotificationDialog = showNotificationDialog, onClose = { showNotificationDialog = false }) @@ -555,17 +554,18 @@ fun EventDetailsBottomSheet( } @Composable -fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: UserViewModel) { +fun EventSaveButton(event: Event?, eventViewModel: EventViewModel, userViewModel: UserViewModel) { val context = LocalContext.current val user by userViewModel.user.collectAsState() + val events by eventViewModel.events.collectAsState() if (user == null) { Log.e("EventCard", "User is null") return } - var isSaved by remember { mutableStateOf(user!!.savedEvents.contains(event.uid)) } + var isSaved by remember { mutableStateOf(user!!.savedEvents.contains(event!!.uid)) } var notificationPermissionsEnabled by remember { mutableStateOf(false) } when (PackageManager.PERMISSION_GRANTED) { @@ -586,12 +586,12 @@ fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: NotificationWorker.schedule( context, UnioNotification( - title = event.title, + title = event!!.title, message = context.getString(R.string.notification_event_reminder), icon = R.drawable.other_icon, channelId = EVENT_REMINDER_CHANNEL_ID, channelName = EVENT_REMINDER_CHANNEL_ID, - notificationId = event.uid.hashCode(), + notificationId = event!!.uid.hashCode(), // Schedule a notification a few hours before the event's startDate timeMillis = (event.startDate.seconds - 2 * SECONDS_IN_AN_HOUR) * SECONDS_IN_AN_HOUR)) } @@ -613,13 +613,13 @@ fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: var enableButton by remember { mutableStateOf(true) } val isConnected = NetworkUtils.checkInternetConnection(context) val onClickSaveButton = { + enableButton = false if (isConnected) { - enableButton = false - eventViewModel.updateSave(event, user!!, isSaved) { + eventViewModel.updateSave(events.first { it.uid == event!!.uid }, user!!, isSaved) { userViewModel.refreshUser() if (isSaved) { if (notificationPermissionsEnabled) { - NotificationWorker.unschedule(context, event.uid.hashCode()) + NotificationWorker.unschedule(context, event!!.uid.hashCode()) } } else { if (!notificationPermissionsEnabled) { @@ -632,43 +632,12 @@ fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: scheduleReminderNotification() } } - enableButton = true isSaved = !isSaved + enableButton = true } - } - - if (isSaved) { - val newEvent = event.copy(numberOfSaved = event.numberOfSaved - 1) - eventViewModel.updateEventWithoutImage( - newEvent, - onSuccess = {}, - onFailure = { e -> Log.e("EventCard", "Failed to update event: $e") }) - userViewModel.unsaveEvent(event) { - if (notificationPermissionsEnabled) { - NotificationWorker.unschedule(context, event.uid.hashCode()) - } - } - Firebase.messaging.unsubscribeFromTopic(event.uid) } else { - val newEvent = event.copy(numberOfSaved = event.numberOfSaved + 1) - eventViewModel.updateEventWithoutImage( - newEvent, - onSuccess = {}, - onFailure = { e -> Log.e("EventCard", "Failed to update event: $e") }) - userViewModel.saveEvent(event) { - if (!notificationPermissionsEnabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // this permission requires api 33 - // We should check how to make notifications work with lower api versions - permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) - } - } else { - scheduleReminderNotification() - } - } - Firebase.messaging.subscribeToTopic(event.uid) + ToastUtils.showToast(context, context.getString(R.string.no_internet_connection)) } - isSaved = !isSaved } IconButton( @@ -678,7 +647,8 @@ fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: .background(MaterialTheme.colorScheme.inversePrimary) .padding(4.dp) .testTag(EventDetailsTestTags.SAVE_BUTTON), - onClick = { onClickSaveButton() }) { + onClick = { onClickSaveButton() }, + enabled = enableButton) { Icon( imageVector = if (isSaved) Icons.Rounded.Favorite else Icons.Rounded.FavoriteBorder, contentDescription = From af49a171225152ba0115e434fb0c177041681212 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 19:02:43 +0100 Subject: [PATCH 09/22] fix: tests with new EventViewModel signature --- .../unio/components/BottomNavigationTest.kt | 8 +++++++- .../unio/components/ScreenDisplayingTest.kt | 8 +++++++- .../association/AssociationProfileTest.kt | 8 +++++++- .../unio/components/event/EventCardTest.kt | 8 +++++++- .../unio/components/event/EventCreationTest.kt | 8 +++++++- .../event/EventDetailsPicturePickerTest.kt | 8 +++++++- .../unio/components/event/EventDetailsTest.kt | 8 +++++++- .../unio/components/event/EventEditTests.kt | 8 +++++++- .../com/android/unio/components/home/HomeTest.kt | 16 ++++++++++++---- .../android/unio/components/map/MapScreenTest.kt | 7 ++++++- .../components/notification/NotificationTest.kt | 7 ++++++- .../android/unio/components/saved/SavedTest.kt | 7 ++++++- .../java/com/android/unio/ui/event/EventCard.kt | 1 - .../com/android/unio/ui/event/EventDetails.kt | 12 ++++++------ .../unio/model/event/EventViewModelTest.kt | 7 ++++++- 15 files changed, 98 insertions(+), 23 deletions(-) diff --git a/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt b/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt index 64741c5a5..be3540236 100644 --- a/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt @@ -9,6 +9,7 @@ import com.android.unio.model.event.EventRepository import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.strings.test_tags.navigation.NavigationActionTestTags @@ -23,6 +24,7 @@ import io.mockk.spyk import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock import org.mockito.kotlin.mock class BottomNavigationTest : TearDown() { @@ -36,6 +38,9 @@ class BottomNavigationTest : TearDown() { @MockK private lateinit var associationRepositoryFirestore: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var userRepository: UserRepository private lateinit var userViewModel: UserViewModel @@ -54,7 +59,8 @@ class BottomNavigationTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) userRepository = mock { UserRepositoryFirestore::class.java } userViewModel = UserViewModel(userRepository, imageRepository) diff --git a/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt b/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt index ed3fe0074..fe5984f05 100644 --- a/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt @@ -23,6 +23,7 @@ import com.android.unio.model.image.ImageViewModel import com.android.unio.model.map.MapViewModel import com.android.unio.model.map.nominatim.NominatimLocationRepository import com.android.unio.model.map.nominatim.NominatimLocationSearchViewModel +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.strings.test_tags.association.AssociationProfileTestTags @@ -75,6 +76,7 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.kotlin.any @@ -113,6 +115,9 @@ class ScreenDisplayingTest : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var imageViewModel: ImageViewModel @@ -161,7 +166,8 @@ class ScreenDisplayingTest : TearDown() { eventRepository, imageRepositoryFirestore, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) eventViewModel.loadEvents() eventViewModel.selectEvent(events.first().uid) diff --git a/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt b/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt index 80bfe16e4..932181d20 100644 --- a/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt @@ -35,6 +35,7 @@ import com.android.unio.model.firestore.firestoreReferenceListWith import com.android.unio.model.follow.ConcurrentAssociationUserRepositoryFirestore import com.android.unio.model.hilt.module.FirebaseModule import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.test_tags.association.AssociationProfileTestTags import com.android.unio.model.user.User import com.android.unio.model.user.UserRepositoryFirestore @@ -70,6 +71,7 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock @HiltAndroidTest @UninstallModules(FirebaseModule::class) @@ -96,6 +98,9 @@ class AssociationProfileTest : TearDown() { @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore @MockK private lateinit var connectivityManager: ConnectivityManager @@ -216,7 +221,8 @@ class AssociationProfileTest : TearDown() { eventRepository, imageRepository, associationRepository, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) every { associationRepository.init(any()) } answers { firstArg<() -> Unit>().invoke() } every { associationRepository.getAssociations(any(), any()) } answers diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt index f7fd5e167..5824cf137 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt @@ -22,6 +22,7 @@ import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.notification.NotificationWorker +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.test_tags.event.EventCardTestTags import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags import com.android.unio.model.user.UserRepositoryFirestore @@ -45,6 +46,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock class EventCardTest : TearDown() { @@ -77,6 +79,9 @@ class EventCardTest : TearDown() { @MockK private lateinit var associationRepository: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var context: Context @Before @@ -94,7 +99,8 @@ class EventCardTest : TearDown() { eventRepository, imageRepository, associationRepository, - eventUserPictureRepositoryFirestore)) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore)) userViewModel = UserViewModel(userRepository, imageRepository) every { userRepository.updateUser(user, any(), any()) } answers { diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt index d61778d41..9f2f62455 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt @@ -26,6 +26,7 @@ import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.map.nominatim.NominatimApiService import com.android.unio.model.map.nominatim.NominatimLocationRepository import com.android.unio.model.map.nominatim.NominatimLocationSearchViewModel +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.strings.TextLengthSamples @@ -50,6 +51,7 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -79,6 +81,9 @@ class EventCreationTest : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore @MockK private lateinit var concurrentAssociationUserRepositoryFirestore: ConcurrentAssociationUserRepositoryFirestore @@ -111,7 +116,8 @@ class EventCreationTest : TearDown() { eventRepository, imageRepositoryFirestore, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) searchViewModel = spyk(SearchViewModel(searchRepository)) associationViewModel = diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt index 377d9c23c..fe3546cc6 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt @@ -14,6 +14,7 @@ import com.android.unio.model.event.EventRepositoryFirestore import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags import com.android.unio.model.user.User import com.android.unio.ui.event.EventDetailsPicturePicker @@ -22,6 +23,7 @@ import io.mockk.impl.annotations.MockK import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock class EventDetailsPicturePickerTest : TearDown() { @@ -33,6 +35,9 @@ class EventDetailsPicturePickerTest : TearDown() { @MockK private lateinit var associationRepository: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel @@ -51,7 +56,8 @@ class EventDetailsPicturePickerTest : TearDown() { eventRepository, imageRepository, associationRepository, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) } @Test diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt index 6f2a66f26..87cc04996 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt @@ -25,6 +25,7 @@ import com.android.unio.model.event.EventUtils.formatTimestamp import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.map.MapViewModel +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.FormatStrings.DAY_MONTH_FORMAT import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags import com.android.unio.model.user.User @@ -47,6 +48,7 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock import org.mockito.Mockito.mock class EventDetailsTest : TearDown() { @@ -66,6 +68,9 @@ class EventDetailsTest : TearDown() { @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel private lateinit var userViewModel: UserViewModel @@ -111,7 +116,8 @@ class EventDetailsTest : TearDown() { eventRepository, imageRepository, associationRepository, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) every { userRepository.init(any()) } returns Unit every { userRepository.getUserWithId("uid", any(), any()) } answers diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt b/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt index a0aa27711..531e81d14 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt @@ -24,6 +24,7 @@ import com.android.unio.model.follow.ConcurrentAssociationUserRepositoryFirestor import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.map.nominatim.NominatimLocationRepository import com.android.unio.model.map.nominatim.NominatimLocationSearchViewModel +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.strings.test_tags.event.EventEditTestTags @@ -44,6 +45,7 @@ import io.mockk.spyk import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.Mock @HiltAndroidTest class EventEditTests : TearDown() { @@ -71,6 +73,9 @@ class EventEditTests : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore @MockK private lateinit var concurrentAssociationUserRepositoryFirestore: ConcurrentAssociationUserRepositoryFirestore @@ -113,7 +118,8 @@ class EventEditTests : TearDown() { eventRepository, imageRepositoryFirestore, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore)) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore)) every { eventViewModel.findEventById(any()) } returns mockEvent eventViewModel.selectEvent(mockEvent.uid) diff --git a/app/src/androidTest/java/com/android/unio/components/home/HomeTest.kt b/app/src/androidTest/java/com/android/unio/components/home/HomeTest.kt index c5affc8a0..89d0c3a61 100644 --- a/app/src/androidTest/java/com/android/unio/components/home/HomeTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/home/HomeTest.kt @@ -22,6 +22,7 @@ import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.hilt.module.FirebaseModule import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.strings.test_tags.home.HomeTestTags @@ -74,6 +75,9 @@ class HomeTest : TearDown() { @MockK private lateinit var associationRepositoryFirestore: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @MockK + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel private lateinit var searchViewModel: SearchViewModel @@ -136,7 +140,8 @@ class HomeTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) eventListFollowed = asso.let { eventList.filter { event -> event.organisers.contains(it.uid) } } } @@ -161,7 +166,8 @@ class HomeTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) ProvidePreferenceLocals { HomeScreen(navigationAction, eventViewModel, userViewModel, searchViewModel) @@ -221,7 +227,8 @@ class HomeTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) ProvidePreferenceLocals { HomeScreen(navigationAction, eventViewModel, userViewModel, searchViewModel) @@ -246,7 +253,8 @@ class HomeTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) ProvidePreferenceLocals { HomeScreen(navigationAction, eventViewModel, userViewModel, searchViewModel) diff --git a/app/src/androidTest/java/com/android/unio/components/map/MapScreenTest.kt b/app/src/androidTest/java/com/android/unio/components/map/MapScreenTest.kt index 292facb28..c3ac924e4 100644 --- a/app/src/androidTest/java/com/android/unio/components/map/MapScreenTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/map/MapScreenTest.kt @@ -18,6 +18,7 @@ import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.map.MapViewModel +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.test_tags.map.MapTestTags import com.android.unio.model.user.User import com.android.unio.model.user.UserRepositoryFirestore @@ -59,6 +60,9 @@ class MapScreenTest : TearDown() { @MockK private lateinit var associationRepositoryFirestore: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @MockK + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var locationTask: Task private lateinit var context: Context @@ -86,7 +90,8 @@ class MapScreenTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) every { userRepository.init(any()) } returns Unit every { userRepository.getUserWithId("123", any(), any()) } answers diff --git a/app/src/androidTest/java/com/android/unio/components/notification/NotificationTest.kt b/app/src/androidTest/java/com/android/unio/components/notification/NotificationTest.kt index 8e20827ad..44a732aa3 100644 --- a/app/src/androidTest/java/com/android/unio/components/notification/NotificationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/notification/NotificationTest.kt @@ -14,6 +14,7 @@ import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage import com.android.unio.model.notification.NotificationWorker import com.android.unio.model.notification.UnioNotification +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.search.SearchRepository import com.android.unio.model.search.SearchViewModel import com.android.unio.model.user.UserRepositoryFirestore @@ -52,6 +53,9 @@ class NotificationTest : TearDown() { @MockK private lateinit var userRepository: UserRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @MockK + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel private lateinit var searchViewModel: SearchViewModel private lateinit var context: Context @@ -78,7 +82,8 @@ class NotificationTest : TearDown() { eventRepository, imageRepository, associationRepository, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) userViewModel = spyk(UserViewModel(userRepository, imageRepository)) } diff --git a/app/src/androidTest/java/com/android/unio/components/saved/SavedTest.kt b/app/src/androidTest/java/com/android/unio/components/saved/SavedTest.kt index ce40bb6b7..129199906 100644 --- a/app/src/androidTest/java/com/android/unio/components/saved/SavedTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/saved/SavedTest.kt @@ -13,6 +13,7 @@ import com.android.unio.model.event.EventRepositoryFirestore import com.android.unio.model.event.EventUserPictureRepositoryFirestore import com.android.unio.model.event.EventViewModel import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.strings.test_tags.saved.SavedTestTags import com.android.unio.model.user.UserRepositoryFirestore import com.android.unio.model.user.UserViewModel @@ -47,6 +48,9 @@ class SavedTest : TearDown() { @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @MockK + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel @@ -95,7 +99,8 @@ class SavedTest : TearDown() { eventRepository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) } @Test diff --git a/app/src/main/java/com/android/unio/ui/event/EventCard.kt b/app/src/main/java/com/android/unio/ui/event/EventCard.kt index 9b1e70005..a46a2e85a 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventCard.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventCard.kt @@ -124,7 +124,6 @@ fun EventCardScaffold( userViewModel: UserViewModel ) { val context = LocalContext.current - val events by eventViewModel.events.collectAsState() Column( diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index 03156b93e..996a73387 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -554,7 +554,7 @@ fun EventDetailsBottomSheet( } @Composable -fun EventSaveButton(event: Event?, eventViewModel: EventViewModel, userViewModel: UserViewModel) { +fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: UserViewModel) { val context = LocalContext.current val user by userViewModel.user.collectAsState() @@ -565,7 +565,7 @@ fun EventSaveButton(event: Event?, eventViewModel: EventViewModel, userViewModel return } - var isSaved by remember { mutableStateOf(user!!.savedEvents.contains(event!!.uid)) } + var isSaved by remember { mutableStateOf(user!!.savedEvents.contains(event.uid)) } var notificationPermissionsEnabled by remember { mutableStateOf(false) } when (PackageManager.PERMISSION_GRANTED) { @@ -586,12 +586,12 @@ fun EventSaveButton(event: Event?, eventViewModel: EventViewModel, userViewModel NotificationWorker.schedule( context, UnioNotification( - title = event!!.title, + title = event.title, message = context.getString(R.string.notification_event_reminder), icon = R.drawable.other_icon, channelId = EVENT_REMINDER_CHANNEL_ID, channelName = EVENT_REMINDER_CHANNEL_ID, - notificationId = event!!.uid.hashCode(), + notificationId = event.uid.hashCode(), // Schedule a notification a few hours before the event's startDate timeMillis = (event.startDate.seconds - 2 * SECONDS_IN_AN_HOUR) * SECONDS_IN_AN_HOUR)) } @@ -615,11 +615,11 @@ fun EventSaveButton(event: Event?, eventViewModel: EventViewModel, userViewModel val onClickSaveButton = { enableButton = false if (isConnected) { - eventViewModel.updateSave(events.first { it.uid == event!!.uid }, user!!, isSaved) { + eventViewModel.updateSave(events.first { it.uid == event.uid }, user!!, isSaved) { userViewModel.refreshUser() if (isSaved) { if (notificationPermissionsEnabled) { - NotificationWorker.unschedule(context, event!!.uid.hashCode()) + NotificationWorker.unschedule(context, event.uid.hashCode()) } } else { if (!notificationPermissionsEnabled) { diff --git a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt index ce14782ad..0b7b17f56 100644 --- a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt +++ b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt @@ -4,6 +4,7 @@ import androidx.test.core.app.ApplicationProvider import com.android.unio.mocks.event.MockEvent import com.android.unio.model.association.AssociationRepositoryFirestore import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore import com.android.unio.model.user.User import com.google.firebase.FirebaseApp import com.google.firebase.Timestamp @@ -40,6 +41,9 @@ class EventViewModelTest { @MockK private lateinit var associationRepositoryFirestore: AssociationRepositoryFirestore @Mock private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @Mock + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore private lateinit var eventViewModel: EventViewModel @@ -87,7 +91,8 @@ class EventViewModelTest { repository, imageRepository, associationRepositoryFirestore, - eventUserPictureRepositoryFirestore) + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore) } @Test From 0863642b30ae675f5862936afdd31c0420890e5a Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 19:21:54 +0100 Subject: [PATCH 10/22] fix tests --- .../android/unio/components/BottomNavigationTest.kt | 3 +-- .../android/unio/components/ScreenDisplayingTest.kt | 3 +-- .../components/association/AssociationProfileTest.kt | 3 +-- .../com/android/unio/components/event/EventCardTest.kt | 3 +-- .../components/event/EventDetailsPicturePickerTest.kt | 3 +-- .../android/unio/components/event/EventDetailsTest.kt | 10 ++++++++-- .../android/unio/components/event/EventEditTests.kt | 3 +-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt b/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt index be3540236..b8ce4b06f 100644 --- a/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt @@ -24,7 +24,6 @@ import io.mockk.spyk import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock import org.mockito.kotlin.mock class BottomNavigationTest : TearDown() { @@ -38,7 +37,7 @@ class BottomNavigationTest : TearDown() { @MockK private lateinit var associationRepositoryFirestore: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore diff --git a/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt b/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt index fe5984f05..a114df663 100644 --- a/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/ScreenDisplayingTest.kt @@ -76,7 +76,6 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.kotlin.any @@ -115,7 +114,7 @@ class ScreenDisplayingTest : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore diff --git a/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt b/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt index 932181d20..33c38b707 100644 --- a/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/association/AssociationProfileTest.kt @@ -71,7 +71,6 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock @HiltAndroidTest @UninstallModules(FirebaseModule::class) @@ -98,7 +97,7 @@ class AssociationProfileTest : TearDown() { @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt index 5824cf137..b93d3a55f 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt @@ -46,7 +46,6 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock class EventCardTest : TearDown() { @@ -79,7 +78,7 @@ class EventCardTest : TearDown() { @MockK private lateinit var associationRepository: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore private lateinit var context: Context diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt index fe3546cc6..eaf967b99 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsPicturePickerTest.kt @@ -23,7 +23,6 @@ import io.mockk.impl.annotations.MockK import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock class EventDetailsPicturePickerTest : TearDown() { @@ -35,7 +34,7 @@ class EventDetailsPicturePickerTest : TearDown() { @MockK private lateinit var associationRepository: AssociationRepositoryFirestore @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt index 87cc04996..ed20bc523 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt @@ -48,7 +48,6 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock import org.mockito.Mockito.mock class EventDetailsTest : TearDown() { @@ -68,7 +67,7 @@ class EventDetailsTest : TearDown() { @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore @@ -119,6 +118,10 @@ class EventDetailsTest : TearDown() { eventUserPictureRepositoryFirestore, concurrentEventUserRepositoryFirestore) + every { eventRepository.getEvents(any(), any()) } answers + { + (it.invocation.args[0] as (List) -> Unit)(events) + } every { userRepository.init(any()) } returns Unit every { userRepository.getUserWithId("uid", any(), any()) } answers { @@ -229,12 +232,15 @@ class EventDetailsTest : TearDown() { @Test fun testButtonBehavior() { setEventScreen(events[0]) + eventViewModel.loadEvents() // Share button composeTestRule .onNodeWithTag(EventDetailsTestTags.SHARE_BUTTON) .assertDisplayComponentInScroll() // Save button + println(events[0].uid) + println(eventViewModel.events.value) composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertDisplayComponentInScroll() composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).performClick() diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt b/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt index 531e81d14..1fdc93883 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventEditTests.kt @@ -45,7 +45,6 @@ import io.mockk.spyk import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock @HiltAndroidTest class EventEditTests : TearDown() { @@ -73,7 +72,7 @@ class EventEditTests : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore @MockK From 98ece2044ee54939547bfaf01cb07cbf8e11f3f9 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 23:22:27 +0100 Subject: [PATCH 11/22] chore: optimise imports --- .../main/java/com/android/unio/model/event/EventViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index 076baeb19..dc3eb687a 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -10,11 +10,11 @@ import com.android.unio.model.user.User import com.google.firebase.Firebase import com.google.firebase.messaging.messaging import dagger.hilt.android.lifecycle.HiltViewModel -import java.io.InputStream -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import java.io.InputStream +import javax.inject.Inject /** * ViewModel class that manages the event list data and provides it to the UI. It uses an From efc8ce516c29e15cdc964678767310e15c5d351c Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 23:25:28 +0100 Subject: [PATCH 12/22] chore: ktfmt --- .../model/association/AssociationViewModel.kt | 1 - .../unio/model/event/EventViewModel.kt | 4 ++-- .../NominatimLocationSearchViewModel.kt | 8 ++++++- .../ui/components/PictureSelectionTool.kt | 23 ++++++++++++++++--- .../com/android/unio/ui/settings/Settings.kt | 1 - .../unio/ui/user/UserClaimAssociation.kt | 2 -- 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt b/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt index c7157599c..e6376e8a4 100644 --- a/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt +++ b/app/src/main/java/com/android/unio/model/association/AssociationViewModel.kt @@ -14,7 +14,6 @@ import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.forEach /** * ViewModel class that manages the association list data and provides it to the UI. It exposes a diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index dc3eb687a..076baeb19 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -10,11 +10,11 @@ import com.android.unio.model.user.User import com.google.firebase.Firebase import com.google.firebase.messaging.messaging import dagger.hilt.android.lifecycle.HiltViewModel +import java.io.InputStream +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import java.io.InputStream -import javax.inject.Inject /** * ViewModel class that manages the event list data and provides it to the UI. It uses an diff --git a/app/src/main/java/com/android/unio/model/map/nominatim/NominatimLocationSearchViewModel.kt b/app/src/main/java/com/android/unio/model/map/nominatim/NominatimLocationSearchViewModel.kt index 2c16aa9c4..fd6db5319 100644 --- a/app/src/main/java/com/android/unio/model/map/nominatim/NominatimLocationSearchViewModel.kt +++ b/app/src/main/java/com/android/unio/model/map/nominatim/NominatimLocationSearchViewModel.kt @@ -8,7 +8,13 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch /** diff --git a/app/src/main/java/com/android/unio/ui/components/PictureSelectionTool.kt b/app/src/main/java/com/android/unio/ui/components/PictureSelectionTool.kt index a5dc22bce..a7702c2b6 100644 --- a/app/src/main/java/com/android/unio/ui/components/PictureSelectionTool.kt +++ b/app/src/main/java/com/android/unio/ui/components/PictureSelectionTool.kt @@ -8,7 +8,17 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.CircleShape @@ -16,8 +26,15 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip diff --git a/app/src/main/java/com/android/unio/ui/settings/Settings.kt b/app/src/main/java/com/android/unio/ui/settings/Settings.kt index 7f7f169f9..320ef8cfc 100644 --- a/app/src/main/java/com/android/unio/ui/settings/Settings.kt +++ b/app/src/main/java/com/android/unio/ui/settings/Settings.kt @@ -24,7 +24,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag diff --git a/app/src/main/java/com/android/unio/ui/user/UserClaimAssociation.kt b/app/src/main/java/com/android/unio/ui/user/UserClaimAssociation.kt index cd55c0c40..4827bbf35 100644 --- a/app/src/main/java/com/android/unio/ui/user/UserClaimAssociation.kt +++ b/app/src/main/java/com/android/unio/ui/user/UserClaimAssociation.kt @@ -15,9 +15,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester From a1e8b96655cecd5d46fe6b5e3035778447a68f60 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 23:35:07 +0100 Subject: [PATCH 13/22] chore: add documentation --- .../com/android/unio/ui/event/EventDetailsPicturePicker.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetailsPicturePicker.kt b/app/src/main/java/com/android/unio/ui/event/EventDetailsPicturePicker.kt index 9d6011df3..447805403 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetailsPicturePicker.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetailsPicturePicker.kt @@ -29,6 +29,13 @@ import com.android.unio.ui.components.PictureSelectionTool import firestoreReferenceElementWith import kotlinx.coroutines.launch +/** + * A Picture picker for Event details screen + * + * @param event the [Event] in question + * @param eventViewModel the [EventViewModel] + * @param user the authenticated [User] + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun EventDetailsPicturePicker(event: Event, eventViewModel: EventViewModel, user: User) { From b547a17564a7dae8f94aaf763299efc108bcaf40 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 23:45:48 +0100 Subject: [PATCH 14/22] chore: remove unused imports --- .../java/com/android/unio/model/event/EventViewModelTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt index 92a2016af..1c60e58eb 100644 --- a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt +++ b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt @@ -5,6 +5,8 @@ import com.android.unio.mocks.event.MockEvent import com.android.unio.mocks.firestore.MockReferenceList import com.android.unio.model.association.AssociationRepositoryFirestore import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore +import com.android.unio.model.strings.StoragePathsStrings import com.android.unio.model.user.User import com.google.firebase.FirebaseApp import com.google.firebase.Timestamp @@ -36,7 +38,6 @@ import org.robolectric.RobolectricTestRunner class EventViewModelTest { @Mock private lateinit var repository: EventRepositoryFirestore @Mock private lateinit var db: FirebaseFirestore - @Mock private lateinit var storage: FirebaseStorage @Mock private lateinit var collectionReference: CollectionReference @Mock private lateinit var inputStream: InputStream From 4ef44340f991513bd1f8b3ed6ffc7e500ae8ab15 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Thu, 12 Dec 2024 23:47:58 +0100 Subject: [PATCH 15/22] chore: unused import --- .../test/java/com/android/unio/model/event/EventViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt index 1c60e58eb..f519c6811 100644 --- a/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt +++ b/app/src/test/java/com/android/unio/model/event/EventViewModelTest.kt @@ -12,7 +12,6 @@ import com.google.firebase.FirebaseApp import com.google.firebase.Timestamp import com.google.firebase.firestore.CollectionReference import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.storage.FirebaseStorage import emptyFirestoreReferenceElement import io.mockk.MockKAnnotations import io.mockk.every From 071555465875674e3b67b35ff62792d951c324a4 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 00:48:11 +0100 Subject: [PATCH 16/22] test: fix tests for EventCard and EventCreation --- .../unio/components/event/EventCardTest.kt | 49 +++++++++++++++++-- .../components/event/EventCreationTest.kt | 2 +- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt index b93d3a55f..6c5fc742f 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt @@ -92,6 +92,8 @@ class EventCardTest : TearDown() { val user = MockUser.createMockUser(followedAssociations = associations, savedEvents = listOf()) every { NotificationWorker.schedule(any(), any()) } just runs every { NotificationWorker.unschedule(any(), any()) } just runs + + eventViewModel = spyk( EventViewModel( @@ -110,6 +112,8 @@ class EventCardTest : TearDown() { every { navigationAction.navigateTo(Screen.EVENT_DETAILS) } just runs every { eventRepository.getEvents(any(), any()) } + + } private fun setEventScreen(event: Event) { @@ -120,10 +124,21 @@ class EventCardTest : TearDown() { } } + private fun setEventViewModel(events: List) { + every { eventRepository.getEvents(any(), any()) } answers + { + (it.invocation.args[0] as (List) -> Unit)(events) + } + eventViewModel.loadEvents() + } + @Test fun testEventCardElementsExist() { + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) + + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_TITLE, useUnmergedTree = true) .assertExists() @@ -164,7 +179,9 @@ class EventCardTest : TearDown() { @Test fun testClickOnEventCard() { + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_TITLE, useUnmergedTree = true) .assertIsDisplayed() @@ -174,8 +191,10 @@ class EventCardTest : TearDown() { @Test fun testImageFallbackDisplayed() { + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) + // Check if the fallback image is displayed when no image is provided composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_IMAGE, useUnmergedTree = true) @@ -185,7 +204,9 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithEmptyUid() { val event = MockEvent.createMockEvent(uid = MockEvent.Companion.EdgeCaseUid.EMPTY.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_TITLE, useUnmergedTree = true) .assertIsDisplayed() // Ensure the title exists @@ -194,7 +215,9 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithEmptyTitle() { val event = MockEvent.createMockEvent(title = MockEvent.Companion.EdgeCaseTitle.EMPTY.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_TITLE, useUnmergedTree = true) .assertTextEquals(MockEvent.Companion.EdgeCaseTitle.EMPTY.value) @@ -203,7 +226,9 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithInvalidImage() { val event = MockEvent.createMockEvent(image = MockEvent.Companion.EdgeCaseImage.INVALID.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_IMAGE, useUnmergedTree = true) .assertExists() // Expect image to use fallback @@ -212,7 +237,9 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithValidImage() { val event = MockEvent.createMockEvent(image = imgUrl) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_IMAGE, useUnmergedTree = true) .assertExists() // Expect image to use fallback @@ -223,7 +250,9 @@ class EventCardTest : TearDown() { val event = MockEvent.createMockEvent( catchyDescription = MockEvent.Companion.EdgeCaseCatchyDescription.EMPTY.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_CATCHY_DESCRIPTION, useUnmergedTree = true) .assertTextEquals("") // Expect empty catchy description @@ -235,7 +264,9 @@ class EventCardTest : TearDown() { MockEvent.createMockEvent( catchyDescription = MockEvent.Companion.EdgeCaseCatchyDescription.SPECIAL_CHARACTERS.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_CATCHY_DESCRIPTION, useUnmergedTree = true) .assertTextEquals(MockEvent.Companion.EdgeCaseCatchyDescription.SPECIAL_CHARACTERS.value) @@ -247,7 +278,10 @@ class EventCardTest : TearDown() { MockEvent.createMockEvent( startDate = MockEvent.Companion.EdgeCaseDate.PAST.value, endDate = MockEvent.Companion.EdgeCaseDate.PAST.value) + + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_DATE, useUnmergedTree = true) .assertExists() @@ -256,7 +290,9 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithTodayStartDate() { val event = MockEvent.createMockEvent(startDate = MockEvent.Companion.EdgeCaseDate.TODAY.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_DATE, useUnmergedTree = true) .assertExists() @@ -265,13 +301,15 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithFutureStartDate() { val event = MockEvent.createMockEvent(startDate = MockEvent.Companion.EdgeCaseDate.FUTURE.value) + setEventViewModel(listOf(event)) setEventScreen(event) + composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_DATE, useUnmergedTree = true) .assertExists() } - @Test + /*@Test fun testEventCardSaveAndUnsaveEventOnline() { var indicator = false every { eventViewModel.updateEventWithoutImage(any(), any(), any()) } answers @@ -282,17 +320,18 @@ class EventCardTest : TearDown() { MockEvent.createMockEvent( startDate = Timestamp(Date((Timestamp.now().seconds + 4 * 3600) * 1000))) + setEventViewModel(listOf(event)) + setEventScreen(event) composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() - Thread.sleep(500) - assert(indicator) + Thread.sleep(3000) + verify { NotificationWorker.schedule(any(), any()) } composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() composeTestRule.waitForIdle() - assert(!indicator) - } + }*/ @After override fun tearDown() { diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt index 9f2f62455..438d30db2 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt @@ -81,7 +81,7 @@ class EventCreationTest : TearDown() { @MockK private lateinit var imageRepositoryFirestore: ImageRepositoryFirebaseStorage @MockK private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore - @Mock + @MockK private lateinit var concurrentEventUserRepositoryFirestore: ConcurrentEventUserRepositoryFirestore @MockK From 75bd50643e6dbd5249fcd1f514d7bc3a77bbce36 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 01:02:05 +0100 Subject: [PATCH 17/22] test: ConcurrentEventUserRepository --- .../unio/components/event/EventCardTest.kt | 44 ++++++++----------- .../components/event/EventCreationTest.kt | 1 - ...currentEventUserRepositoryFirestoreTest.kt | 40 +++++++++++++++++ 3 files changed, 59 insertions(+), 26 deletions(-) create mode 100644 app/src/test/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestoreTest.kt diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt index 6c5fc742f..2490b33bf 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCardTest.kt @@ -93,7 +93,6 @@ class EventCardTest : TearDown() { every { NotificationWorker.schedule(any(), any()) } just runs every { NotificationWorker.unschedule(any(), any()) } just runs - eventViewModel = spyk( EventViewModel( @@ -112,8 +111,6 @@ class EventCardTest : TearDown() { every { navigationAction.navigateTo(Screen.EVENT_DETAILS) } just runs every { eventRepository.getEvents(any(), any()) } - - } private fun setEventScreen(event: Event) { @@ -124,21 +121,19 @@ class EventCardTest : TearDown() { } } - private fun setEventViewModel(events: List) { - every { eventRepository.getEvents(any(), any()) } answers - { - (it.invocation.args[0] as (List) -> Unit)(events) - } - eventViewModel.loadEvents() - } + private fun setEventViewModel(events: List) { + every { eventRepository.getEvents(any(), any()) } answers + { + (it.invocation.args[0] as (List) -> Unit)(events) + } + eventViewModel.loadEvents() + } @Test fun testEventCardElementsExist() { - setEventViewModel(listOf(sampleEvent)) + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) - - composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_TITLE, useUnmergedTree = true) .assertExists() @@ -179,7 +174,7 @@ class EventCardTest : TearDown() { @Test fun testClickOnEventCard() { - setEventViewModel(listOf(sampleEvent)) + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) composeTestRule @@ -191,10 +186,9 @@ class EventCardTest : TearDown() { @Test fun testImageFallbackDisplayed() { - setEventViewModel(listOf(sampleEvent)) + setEventViewModel(listOf(sampleEvent)) setEventScreen(sampleEvent) - // Check if the fallback image is displayed when no image is provided composeTestRule .onNodeWithTag(EventCardTestTags.EVENT_IMAGE, useUnmergedTree = true) @@ -204,7 +198,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithEmptyUid() { val event = MockEvent.createMockEvent(uid = MockEvent.Companion.EdgeCaseUid.EMPTY.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -215,7 +209,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithEmptyTitle() { val event = MockEvent.createMockEvent(title = MockEvent.Companion.EdgeCaseTitle.EMPTY.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -226,7 +220,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithInvalidImage() { val event = MockEvent.createMockEvent(image = MockEvent.Companion.EdgeCaseImage.INVALID.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -237,7 +231,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithValidImage() { val event = MockEvent.createMockEvent(image = imgUrl) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -250,7 +244,7 @@ class EventCardTest : TearDown() { val event = MockEvent.createMockEvent( catchyDescription = MockEvent.Companion.EdgeCaseCatchyDescription.EMPTY.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -264,7 +258,7 @@ class EventCardTest : TearDown() { MockEvent.createMockEvent( catchyDescription = MockEvent.Companion.EdgeCaseCatchyDescription.SPECIAL_CHARACTERS.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -279,7 +273,7 @@ class EventCardTest : TearDown() { startDate = MockEvent.Companion.EdgeCaseDate.PAST.value, endDate = MockEvent.Companion.EdgeCaseDate.PAST.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -290,7 +284,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithTodayStartDate() { val event = MockEvent.createMockEvent(startDate = MockEvent.Companion.EdgeCaseDate.TODAY.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule @@ -301,7 +295,7 @@ class EventCardTest : TearDown() { @Test fun testEventCardWithFutureStartDate() { val event = MockEvent.createMockEvent(startDate = MockEvent.Companion.EdgeCaseDate.FUTURE.value) - setEventViewModel(listOf(event)) + setEventViewModel(listOf(event)) setEventScreen(event) composeTestRule diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt index 438d30db2..f41768120 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventCreationTest.kt @@ -51,7 +51,6 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.Mock import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory diff --git a/app/src/test/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestoreTest.kt b/app/src/test/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestoreTest.kt new file mode 100644 index 000000000..6aecf8d56 --- /dev/null +++ b/app/src/test/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestoreTest.kt @@ -0,0 +1,40 @@ +package com.android.unio.model.save + +import com.android.unio.mocks.event.MockEvent +import com.android.unio.mocks.user.MockUser +import com.android.unio.model.event.EventRepository +import com.android.unio.model.user.UserRepository +import com.google.firebase.firestore.FirebaseFirestore +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import org.junit.Before +import org.junit.Test + +class ConcurrentEventUserRepositoryFirestoreTest { + + @MockK private lateinit var db: FirebaseFirestore + @MockK private lateinit var userRepository: UserRepository + @MockK private lateinit var eventRepository: EventRepository + + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore + + private val user = MockUser.createMockUser(uid = "1") + private val event = MockEvent.createMockEvent(uid = "11") + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + + concurrentEventUserRepositoryFirestore = + ConcurrentEventUserRepositoryFirestore(db, userRepository, eventRepository) + } + + @Test + fun testUpdateSave() { + // Not very thorough testing but complicated to test more + concurrentEventUserRepositoryFirestore.updateSave(user, event, {}, {}) + verify { db.runBatch(any()) } + } +} From cdb6754eb404319bcaecc62cdca3715febfb2365 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 01:49:08 +0100 Subject: [PATCH 18/22] test: EventSaveButton --- .../components/event/EventSaveButtonTest.kt | 119 ++++++++++++++++++ .../unio/model/event/EventViewModel.kt | 2 - .../ConcurrentEventUserRepositoryFirestore.kt | 1 - 3 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt new file mode 100644 index 000000000..36d11d5f9 --- /dev/null +++ b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt @@ -0,0 +1,119 @@ +package com.android.unio.components.event + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.test.rule.GrantPermissionRule +import com.android.unio.TearDown +import com.android.unio.mocks.event.MockEvent +import com.android.unio.mocks.user.MockUser +import com.android.unio.model.association.AssociationRepositoryFirestore +import com.android.unio.model.event.Event +import com.android.unio.model.event.EventRepositoryFirestore +import com.android.unio.model.event.EventUserPictureRepositoryFirestore +import com.android.unio.model.event.EventViewModel +import com.android.unio.model.image.ImageRepositoryFirebaseStorage +import com.android.unio.model.notification.NotificationWorker +import com.android.unio.model.save.ConcurrentEventUserRepositoryFirestore +import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags +import com.android.unio.model.user.User +import com.android.unio.model.user.UserRepositoryFirestore +import com.android.unio.model.user.UserViewModel +import com.android.unio.ui.event.EventSaveButton +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockkObject +import io.mockk.spyk +import io.mockk.verify +import me.zhanghai.compose.preference.ProvidePreferenceLocals +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class EventSaveButtonTest : TearDown() { + @get:Rule val composeTestRule = createComposeRule() + + @get:Rule + val permissionRule: GrantPermissionRule = + GrantPermissionRule.grant(android.Manifest.permission.POST_NOTIFICATIONS) + + @MockK private lateinit var eventRepository: EventRepositoryFirestore + @MockK private lateinit var imageRepository: ImageRepositoryFirebaseStorage + @MockK private lateinit var associationRepository: AssociationRepositoryFirestore + @MockK + private lateinit var eventUserPictureRepositoryFirestore: EventUserPictureRepositoryFirestore + @MockK + private lateinit var concurrentEventUserRepositoryFirestore: + ConcurrentEventUserRepositoryFirestore + @MockK private lateinit var userRepository: UserRepositoryFirestore + private lateinit var eventViewModel: EventViewModel + private lateinit var userViewModel: UserViewModel + + private val testEvent = MockEvent.createMockEvent(uid = "1") + private val testUser = MockUser.createMockUser(uid = "1") + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + mockkObject(NotificationWorker.Companion) + eventViewModel = + spyk( + EventViewModel( + eventRepository, + imageRepository, + associationRepository, + eventUserPictureRepositoryFirestore, + concurrentEventUserRepositoryFirestore)) + + userViewModel = UserViewModel(userRepository, imageRepository) + every { userRepository.updateUser(testUser, any(), any()) } answers + { + val onSuccess = args[1] as () -> Unit + onSuccess() + } + userViewModel.addUser(testUser) {} + + every { eventRepository.getEvents(any(), any()) } answers + { + (it.invocation.args[0] as (List) -> Unit)(listOf(testEvent)) + } + + every { userRepository.getUserWithId(testUser.uid, {}, {}) } answers + { + val onSuccess = args[1] as (User) -> Unit + onSuccess(testUser) + } + + eventViewModel.loadEvents() + } + + private fun setEventSaveButton() { + composeTestRule.setContent { + ProvidePreferenceLocals { EventSaveButton(testEvent, eventViewModel, userViewModel) } + } + } + + @Test + fun testEventCardSaveAndUnsaveEventOnline() { + var indicator = false + every { concurrentEventUserRepositoryFirestore.updateSave(any(), any(), any(), any()) } answers + { + val onSuccess = args[2] as () -> Unit + onSuccess() + indicator = !indicator + } + + setEventSaveButton() + composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() + + Thread.sleep(500) + assert(indicator) + + verify { NotificationWorker.schedule(any(), any()) } + + composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() + composeTestRule.waitForIdle() + assert(!indicator) + } +} diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index 14f90a03e..0c105df2f 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -323,8 +323,6 @@ constructor( updatedUser.savedEvents.add(target.uid) Firebase.messaging.subscribeToTopic(target.uid) } - println("target ${target.numberOfSaved}") - println(updatedEvent.numberOfSaved) concurrentEventUserRepository.updateSave( updatedUser, updatedEvent, diff --git a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt index 64020fa0e..baea82e60 100644 --- a/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt +++ b/app/src/main/java/com/android/unio/model/save/ConcurrentEventUserRepositoryFirestore.kt @@ -52,7 +52,6 @@ constructor( onSuccess: () -> Unit, onFailure: (Exception) -> Unit ) { - // TODO: test method db.runBatch { batch -> val userRef = userRepository.getUserRef(user.uid) val eventRef = eventRepository.getEventRef(event.uid) From 25ef5d823355bfbc54da53a6252de694ff5f9002 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 03:24:09 +0100 Subject: [PATCH 19/22] chore: ktfmt --- .../unio/model/event/EventViewModel.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt index 3175cadc2..1b08faba4 100644 --- a/app/src/main/java/com/android/unio/model/event/EventViewModel.kt +++ b/app/src/main/java/com/android/unio/model/event/EventViewModel.kt @@ -185,18 +185,22 @@ constructor( fun updateEventWithoutImage(event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) { repository.addEvent(event, onSuccess, onFailure) - event.organisers.requestAll({ - event.organisers.list.value.forEach { - if (it.events.contains(event.uid)) it.events.remove(event.uid) - it.events.add(event.uid) - associationRepository.saveAssociation( - isNewAssociation = false, - it, - {}, - { e -> Log.e("EventViewModel", "An error occurred while loading associations: $e") }) - it.events.requestAll() - } - }, lazy = true) + event.organisers.requestAll( + { + event.organisers.list.value.forEach { + if (it.events.contains(event.uid)) it.events.remove(event.uid) + it.events.add(event.uid) + associationRepository.saveAssociation( + isNewAssociation = false, + it, + {}, + { e -> + Log.e("EventViewModel", "An error occurred while loading associations: $e") + }) + it.events.requestAll() + } + }, + lazy = true) _events.value = _events.value.filter { it.uid != event.uid } // Remove the outdated event _events.value += event From 8c36edf244d034276aa623acc1f8579429ab698f Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 03:25:58 +0100 Subject: [PATCH 20/22] chore: change Event? to Event --- .../java/com/android/unio/ui/event/EventDetails.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt index 996a73387..182f6e3be 100644 --- a/app/src/main/java/com/android/unio/ui/event/EventDetails.kt +++ b/app/src/main/java/com/android/unio/ui/event/EventDetails.kt @@ -159,7 +159,7 @@ fun EventScreen( fun EventScreenScaffold( navigationAction: NavigationAction, mapViewModel: MapViewModel, - event: Event?, + event: Event, organisers: List, eventViewModel: EventViewModel, userViewModel: UserViewModel @@ -177,7 +177,7 @@ fun EventScreenScaffold( Scaffold( floatingActionButton = { if (pagerState.currentPage == 1) { - EventDetailsPicturePicker(event!!, eventViewModel, user!!) // Asserted non null above + EventDetailsPicturePicker(event, eventViewModel, user!!) // Asserted non null above } }, modifier = Modifier.testTag(EventDetailsTestTags.SCREEN), @@ -208,7 +208,7 @@ fun EventScreenScaffold( } }, actions = { - EventSaveButton(event!!, eventViewModel, userViewModel) + EventSaveButton(event, eventViewModel, userViewModel) IconButton( modifier = Modifier.testTag(EventDetailsTestTags.SHARE_BUTTON), onClick = { showSheet = true }) { @@ -220,13 +220,13 @@ fun EventScreenScaffold( }) }, content = { - EventScreenContent(navigationAction, mapViewModel, event!!, organisers, pagerState, tabList) + EventScreenContent(navigationAction, mapViewModel, event, organisers, pagerState, tabList) }) NotificationSender( dialogTitle = context.getString(R.string.event_send_notification), notificationType = NotificationType.EVENT_SAVERS, - topic = event!!.uid, + topic = event.uid, notificationContent = { mapOf("title" to event.title, "body" to it) }, showNotificationDialog = showNotificationDialog, onClose = { showNotificationDialog = false }) From 9efaf7dd94a143dfbf48e40d03275538f469ca8c Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 03:37:05 +0100 Subject: [PATCH 21/22] chore: add comments --- .../android/unio/components/event/EventSaveButtonTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt index 36d11d5f9..37d2c3c65 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt @@ -96,7 +96,7 @@ class EventSaveButtonTest : TearDown() { @Test fun testEventCardSaveAndUnsaveEventOnline() { - var indicator = false + var indicator = false //saved indicator every { concurrentEventUserRepositoryFirestore.updateSave(any(), any(), any(), any()) } answers { val onSuccess = args[2] as () -> Unit @@ -108,12 +108,12 @@ class EventSaveButtonTest : TearDown() { composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() Thread.sleep(500) - assert(indicator) + assert(indicator) // asserts event is saved - verify { NotificationWorker.schedule(any(), any()) } + verify { NotificationWorker.schedule(any(), any()) } // asserts that a notification is scheduled composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertExists().performClick() composeTestRule.waitForIdle() - assert(!indicator) + assert(!indicator) // asserts event is unsaved } } From 9f7fbd97087bfdf40585ecb1d882bd8fa6518085 Mon Sep 17 00:00:00 2001 From: Arnaud Rajon Date: Fri, 13 Dec 2024 03:38:34 +0100 Subject: [PATCH 22/22] chore: ktfmt --- .../com/android/unio/components/event/EventSaveButtonTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt index 37d2c3c65..45068d0e5 100644 --- a/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt +++ b/app/src/androidTest/java/com/android/unio/components/event/EventSaveButtonTest.kt @@ -96,7 +96,7 @@ class EventSaveButtonTest : TearDown() { @Test fun testEventCardSaveAndUnsaveEventOnline() { - var indicator = false //saved indicator + var indicator = false // saved indicator every { concurrentEventUserRepositoryFirestore.updateSave(any(), any(), any(), any()) } answers { val onSuccess = args[2] as () -> Unit