Skip to content

Commit 7edf610

Browse files
authored
Merge pull request #315 from SwEnt-Group13/feature/delete-and-like-pictures
Feature: Allow users to delete and like event pictures
2 parents 445e977 + b5dfc3c commit 7edf610

File tree

24 files changed

+879
-220
lines changed

24 files changed

+879
-220
lines changed

app/src/androidTest/java/com/android/unio/components/BottomNavigationTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import com.android.unio.model.user.UserViewModel
2020
import com.android.unio.ui.home.HomeScreen
2121
import com.android.unio.ui.navigation.NavigationAction
2222
import io.mockk.MockKAnnotations
23+
import io.mockk.every
2324
import io.mockk.impl.annotations.MockK
25+
import io.mockk.just
26+
import io.mockk.runs
2427
import io.mockk.spyk
2528
import org.junit.Before
2629
import org.junit.Rule
@@ -66,6 +69,8 @@ class BottomNavigationTest : TearDown() {
6669

6770
searchViewModel = spyk(SearchViewModel(searchRepository))
6871

72+
every { userDeletionRepository.init {} } just runs
73+
6974
composeTestRule.setContent {
7075
HomeScreen(navigationAction, eventViewModel, userViewModel, searchViewModel)
7176
}

app/src/androidTest/java/com/android/unio/components/event/EventDetailsTest.kt

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.android.unio.components.event
22

33
import android.content.ContentResolver
4+
import android.content.Context
45
import android.content.res.Resources
56
import android.net.Uri
67
import androidx.annotation.AnyRes
@@ -23,6 +24,7 @@ import com.android.unio.TearDown
2324
import com.android.unio.assertDisplayComponentInScroll
2425
import com.android.unio.mocks.association.MockAssociation
2526
import com.android.unio.mocks.event.MockEvent
27+
import com.android.unio.mocks.firestore.MockReferenceElement
2628
import com.android.unio.mocks.firestore.MockReferenceList
2729
import com.android.unio.mocks.user.MockUser
2830
import com.android.unio.model.association.Association
@@ -33,6 +35,7 @@ import com.android.unio.model.event.EventUserPicture
3335
import com.android.unio.model.event.EventUserPictureRepositoryFirestore
3436
import com.android.unio.model.event.EventUtils.formatTimestamp
3537
import com.android.unio.model.event.EventViewModel
38+
import com.android.unio.model.firestore.emptyFirestoreReferenceList
3639
import com.android.unio.model.image.ImageRepositoryFirebaseStorage
3740
import com.android.unio.model.map.MapViewModel
3841
import com.android.unio.model.strings.FormatStrings.DAY_MONTH_FORMAT
@@ -42,6 +45,7 @@ import com.android.unio.model.usecase.UserDeletionUseCaseFirestore
4245
import com.android.unio.model.user.User
4346
import com.android.unio.model.user.UserRepositoryFirestore
4447
import com.android.unio.model.user.UserViewModel
48+
import com.android.unio.ui.event.EventDetailsPicturesTab
4549
import com.android.unio.ui.event.EventScreenScaffold
4650
import com.android.unio.ui.navigation.NavigationAction
4751
import com.android.unio.ui.navigation.Screen
@@ -66,8 +70,10 @@ class EventDetailsTest : TearDown() {
6670
private lateinit var navigationAction: NavigationAction
6771

6872
private lateinit var events: List<Event>
73+
private lateinit var user: User
6974
private lateinit var eventPictures: List<EventUserPicture>
7075
private lateinit var associations: List<Association>
76+
private lateinit var context: Context
7177

7278
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
7379
private lateinit var mapViewModel: MapViewModel
@@ -98,20 +104,28 @@ class EventDetailsTest : TearDown() {
98104
@Before
99105
fun setUp() {
100106
MockKAnnotations.init(this, relaxed = true)
101-
val context = InstrumentationRegistry.getInstrumentation().targetContext
107+
context = InstrumentationRegistry.getInstrumentation().targetContext
102108
val resources = context.applicationContext.resources
109+
user = MockUser.createMockUser(uid = "moi")
110+
103111
eventPictures =
104112
listOf(
105113
EventUserPicture(
106114
"12",
107115
resources.getUri(R.drawable.placeholder_pictures).toString(),
108116
User.emptyFirestoreReferenceElement(),
109-
0),
117+
User.emptyFirestoreReferenceList()),
110118
EventUserPicture(
111119
"34",
112120
resources.getUri(R.drawable.placeholder_pictures).toString(),
113121
User.emptyFirestoreReferenceElement(),
114-
3))
122+
User.emptyFirestoreReferenceList()),
123+
EventUserPicture(
124+
"56",
125+
resources.getUri(R.drawable.placeholder_pictures).toString(),
126+
MockReferenceElement(user),
127+
User.emptyFirestoreReferenceList()),
128+
)
115129
events =
116130
listOf(
117131
MockEvent.createMockEvent(
@@ -158,11 +172,12 @@ class EventDetailsTest : TearDown() {
158172
}
159173

160174
userViewModel = UserViewModel(userRepository, imageRepository, userDeletionRepository)
161-
userViewModel.getUserByUid("uid")
175+
userViewModel.addUser(user) { userViewModel.refreshUser() }
176+
177+
// every { userRepository.updateUser(user, any(), any()) } returns Unit
162178
}
163179

164180
private fun setEventScreen(event: Event) {
165-
166181
composeTestRule.setContent {
167182
ProvidePreferenceLocals {
168183
EventScreenScaffold(
@@ -269,8 +284,6 @@ class EventDetailsTest : TearDown() {
269284
.assertDisplayComponentInScroll()
270285

271286
// Save button
272-
println(events[0].uid)
273-
println(eventViewModel.events.value)
274287
composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).assertDisplayComponentInScroll()
275288
composeTestRule.onNodeWithTag(EventDetailsTestTags.SAVE_BUTTON).performClick()
276289

@@ -354,7 +367,45 @@ class EventDetailsTest : TearDown() {
354367

355368
@Test
356369
fun testFullSizePictureOnClick() {
370+
eventViewModel.loadEvents()
371+
eventViewModel.selectEvent(events[0].uid, true)
372+
357373
setEventScreen(events[0])
374+
375+
goToGallery()
376+
composeTestRule.waitUntil(5000) {
377+
composeTestRule
378+
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[0].uid)
379+
.isDisplayed()
380+
}
381+
382+
composeTestRule
383+
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[0].uid)
384+
.performClick()
385+
386+
composeTestRule.onNodeWithTag(EventDetailsTestTags.PICTURE_FULL_SCREEN).assertIsDisplayed()
387+
composeTestRule
388+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_LEFT)
389+
.assertIsDisplayed()
390+
391+
composeTestRule
392+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_LIKE_BUTTON)
393+
.assertIsDisplayed()
394+
composeTestRule
395+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_LIKE_COUNTER)
396+
.assertIsDisplayed()
397+
composeTestRule
398+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_AUTHOR_INFO)
399+
.assertIsDisplayed()
400+
}
401+
402+
@Test
403+
fun testLikePicture() {
404+
eventViewModel.loadEvents()
405+
eventViewModel.selectEvent(events[0].uid, true)
406+
407+
setEventScreen(events[0])
408+
358409
goToGallery()
359410
composeTestRule.waitUntil(5000) {
360411
composeTestRule
@@ -373,5 +424,40 @@ class EventDetailsTest : TearDown() {
373424
composeTestRule
374425
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_RIGHT)
375426
.assertIsDisplayed()
427+
.performClick()
428+
429+
composeTestRule
430+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_LIKE_BUTTON)
431+
.assertIsDisplayed()
432+
.performClick()
433+
Thread.sleep(500)
434+
composeTestRule
435+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_LIKE_COUNTER)
436+
.assertTextEquals("1")
437+
}
438+
439+
@Test
440+
fun testDeletePicture() {
441+
eventViewModel.loadEvents()
442+
eventViewModel.selectEvent(events[0].uid, true)
443+
composeTestRule.setContent {
444+
ProvidePreferenceLocals { EventDetailsPicturesTab(events[0], user, context, eventViewModel) }
445+
}
446+
composeTestRule.waitUntil(5000) {
447+
composeTestRule
448+
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[2].uid)
449+
.isDisplayed()
450+
}
451+
452+
composeTestRule
453+
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[2].uid)
454+
.performClick()
455+
456+
composeTestRule.onNodeWithTag(EventDetailsTestTags.PICTURE_FULL_SCREEN).assertIsDisplayed()
457+
Thread.sleep(1000)
458+
composeTestRule
459+
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_DELETE_BUTTON)
460+
.assertIsDisplayed()
461+
.performClick()
376462
}
377463
}

app/src/main/java/com/android/unio/MainActivity.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ class MainActivity : ComponentActivity() {
6969
override fun onCreate(savedInstanceState: Bundle?) {
7070
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
7171
super.onCreate(savedInstanceState)
72-
7372
setContent {
7473
Surface(modifier = Modifier.fillMaxSize()) {
7574
ProvidePreferenceLocals { AppTheme { UnioApp() } }

app/src/main/java/com/android/unio/model/event/EventUserPicture.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.android.unio.model.event
22

33
import com.android.unio.model.firestore.ReferenceElement
4+
import com.android.unio.model.firestore.ReferenceList
45
import com.android.unio.model.firestore.UniquelyIdentifiable
56
import com.android.unio.model.user.User
67

@@ -16,7 +17,7 @@ data class EventUserPicture(
1617
override val uid: String,
1718
val image: String,
1819
val author: ReferenceElement<User>,
19-
val likes: Int
20+
val likes: ReferenceList<User>
2021
) : UniquelyIdentifiable {
2122
companion object
2223
}

app/src/main/java/com/android/unio/model/event/EventViewModel.kt

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -212,25 +212,32 @@ constructor(
212212
* @param onSuccess A callback that is called when the event is successfully updated.
213213
* @param onFailure A callback that is called when an error occurs while updating the event.
214214
*/
215-
fun updateEventWithoutImage(event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
215+
fun updateEventWithoutImage(
216+
event: Event,
217+
onSuccess: () -> Unit,
218+
onFailure: (Exception) -> Unit,
219+
updateAssociation: Boolean = true
220+
) {
216221
repository.addEvent(event, onSuccess, onFailure)
217222

218-
event.organisers.requestAll(
219-
{
220-
event.organisers.list.value.forEach {
221-
if (it.events.contains(event.uid)) it.events.remove(event.uid)
222-
it.events.add(event.uid)
223-
associationRepository.saveAssociation(
224-
isNewAssociation = false,
225-
it,
226-
{},
227-
{ e ->
228-
Log.e("EventViewModel", "An error occurred while loading associations: $e")
229-
})
230-
it.events.requestAll()
231-
}
232-
},
233-
lazy = true)
223+
if (updateAssociation) {
224+
event.organisers.requestAll(
225+
{
226+
event.organisers.list.value.forEach {
227+
if (it.events.contains(event.uid)) it.events.remove(event.uid)
228+
it.events.add(event.uid)
229+
associationRepository.saveAssociation(
230+
isNewAssociation = false,
231+
it,
232+
{},
233+
{ e ->
234+
Log.e("EventViewModel", "An error occurred while loading associations: $e")
235+
})
236+
it.events.requestAll()
237+
}
238+
},
239+
lazy = true)
240+
}
234241

235242
_events.value = _events.value.filter { it.uid != event.uid } // Remove the outdated event
236243
_events.value += event
@@ -306,7 +313,9 @@ constructor(
306313
fun addEventUserPicture(
307314
pictureInputStream: InputStream,
308315
event: Event,
309-
picture: EventUserPicture
316+
picture: EventUserPicture,
317+
onSuccess: () -> Unit,
318+
onFailure: (Exception) -> Unit
310319
) {
311320
val picId = eventUserPictureRepository.getNewUid()
312321
imageRepository.uploadImage(
@@ -317,21 +326,86 @@ constructor(
317326
eventUserPictureRepository.addEventUserPicture(
318327
newEventPicture,
319328
{
320-
event.eventPictures.add(newEventPicture.uid)
329+
event.eventPictures.add(newEventPicture)
321330
updateEventWithoutImage(
322331
event,
323-
{ event.eventPictures.add(newEventPicture) },
332+
{ onSuccess() },
324333
{ e ->
325334
Log.e("EventViewModel", "An error occurred while updating an event: $e")
326-
})
335+
},
336+
false)
327337
},
328338
{ e ->
339+
onFailure(e)
329340
Log.e("EventViewModel", "An error occurred while adding an event picture: $e")
330341
})
331342
},
332343
onFailure = { e -> Log.e("ImageRepository", "Failed to store image: $e") })
333344
}
334345

346+
/**
347+
* Update an existing eventUserPicture without updating its image.
348+
*
349+
* @param event The event in question.
350+
* @param picture The [EventUserPicture] to update
351+
*/
352+
fun updateEventUserPictureWithoutImage(
353+
event: Event,
354+
picture: EventUserPicture,
355+
onSuccess: () -> Unit,
356+
onFailure: (Exception) -> Unit
357+
) {
358+
eventUserPictureRepository.addEventUserPicture(
359+
picture,
360+
{
361+
if (!event.eventPictures.contains(picture.uid)) {
362+
event.eventPictures.add(picture)
363+
}
364+
updateEventWithoutImage(
365+
event,
366+
{
367+
_events.value = _events.value.map { if (it.uid == event.uid) event else it }
368+
onSuccess()
369+
},
370+
{ e ->
371+
onFailure(e)
372+
Log.e("EventViewModel", "An error occurred while updating an event: $e")
373+
},
374+
false)
375+
},
376+
{ e ->
377+
onFailure(e)
378+
Log.e("EventViewModel", "An error occurred while adding an event picture: $e")
379+
})
380+
}
381+
382+
fun deleteEventUserPicture(
383+
uid: String,
384+
event: Event,
385+
onSuccess: () -> Unit,
386+
onFailure: (Exception) -> Unit
387+
) {
388+
389+
eventUserPictureRepository.deleteEventUserPictureById(
390+
uid,
391+
{
392+
event.eventPictures.remove(uid)
393+
updateEventWithoutImage(
394+
event,
395+
{
396+
_events.value = _events.value.map { if (it.uid == event.uid) event else it }
397+
onSuccess()
398+
},
399+
{ e ->
400+
onFailure(e)
401+
Log.e("EventViewModel", "An error occurred while updating an event: $e")
402+
},
403+
false)
404+
onSuccess()
405+
},
406+
onFailure)
407+
}
408+
335409
/**
336410
* Updates the save status of the user for the target event. If the user has already saved the
337411
* event, the event's interested count is decremented and the event is removed from the user's

app/src/main/java/com/android/unio/model/firestore/transform/Hydration.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,12 @@ fun EventUserPictureRepositoryFirestore.Companion.hydrate(
170170
data: Map<String, Any>?
171171
): EventUserPicture {
172172
val author = data?.get(EventUserPicture::author.name) as? String ?: ""
173+
val likes =
174+
User.firestoreReferenceListWith(
175+
data?.get(EventUserPicture::likes.name) as? List<String> ?: emptyList())
173176
return EventUserPicture(
174177
uid = data?.get(EventUserPicture::uid.name) as? String ?: "",
175178
author = User.firestoreReferenceElementWith(author),
176179
image = data?.get(EventUserPicture::image.name) as? String ?: "",
177-
likes = data?.get(EventUserPicture::likes.name) as? Int ?: 0,
178-
)
180+
likes = likes)
179181
}

0 commit comments

Comments
 (0)