Skip to content

Commit

Permalink
Merge pull request #304 from SwEnt-Group13/feature/maximise_picture_o…
Browse files Browse the repository at this point in the history
…nclick

Feature: maximise event picture when clicking on it
  • Loading branch information
armouldr authored Dec 16, 2024
2 parents d1b7aa4 + 1b84bb2 commit 2c248c9
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.android.unio.components.event

import android.content.ContentResolver
import android.content.res.Resources
import android.net.Uri
import androidx.annotation.AnyRes
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.navigation.NavHostController
import androidx.test.platform.app.InstrumentationRegistry
import com.android.unio.R
import com.android.unio.TearDown
import com.android.unio.assertDisplayComponentInScroll
import com.android.unio.mocks.association.MockAssociation
Expand Down Expand Up @@ -76,13 +83,32 @@ class EventDetailsTest : TearDown() {

@get:Rule val composeTestRule = createComposeRule()

private fun Resources.getUri(@AnyRes int: Int): Uri {
val scheme = ContentResolver.SCHEME_ANDROID_RESOURCE
val pkg = getResourcePackageName(int)
val type = getResourceTypeName(int)
val name = getResourceEntryName(int)
val uri = "$scheme://$pkg/$type/$name"
return Uri.parse(uri)
}

@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
val context = InstrumentationRegistry.getInstrumentation().targetContext
val resources = context.applicationContext.resources
eventPictures =
listOf(
EventUserPicture("12", "http:image.com", User.emptyFirestoreReferenceElement(), 0),
EventUserPicture("34", "http:image.com", User.emptyFirestoreReferenceElement(), 3))
EventUserPicture(
"12",
resources.getUri(R.drawable.placeholder_pictures).toString(),
User.emptyFirestoreReferenceElement(),
0),
EventUserPicture(
"34",
resources.getUri(R.drawable.placeholder_pictures).toString(),
User.emptyFirestoreReferenceElement(),
3))
events =
listOf(
MockEvent.createMockEvent(
Expand Down Expand Up @@ -276,13 +302,19 @@ class EventDetailsTest : TearDown() {
composeTestRule.onNodeWithTag(EventDetailsTestTags.START_DATE).assertDisplayComponentInScroll()
}

@Test
fun testGalleryDisplays() {
setEventScreen(events[0])
private fun goToGallery() {
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER)
.assertDisplayComponentInScroll()
composeTestRule.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER).performScrollToIndex(1)
}

@Test
fun testGalleryDisplays() {
setEventScreen(events[0])

goToGallery()

composeTestRule
.onNodeWithTag(EventDetailsTestTags.GALLERY_GRID)
.assertDisplayComponentInScroll()
Expand All @@ -291,10 +323,7 @@ class EventDetailsTest : TearDown() {
@Test
fun testGalleryDoesNotDisplayWhenFutureStartDate() {
setEventScreen(events[1])
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER)
.assertDisplayComponentInScroll()
composeTestRule.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER).performScrollToIndex(1)
goToGallery()
composeTestRule.onNodeWithTag(EventDetailsTestTags.GALLERY_GRID).assertIsNotDisplayed()
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_NOT_STARTED_TEXT)
Expand All @@ -304,13 +333,33 @@ class EventDetailsTest : TearDown() {
@Test
fun testGalleryDoesNotDisplayWhenNoPictures() {
setEventScreen(events[2])
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER)
.assertDisplayComponentInScroll()
composeTestRule.onNodeWithTag(EventDetailsTestTags.EVENT_DETAILS_PAGER).performScrollToIndex(1)
goToGallery()
composeTestRule.onNodeWithTag(EventDetailsTestTags.GALLERY_GRID).assertIsNotDisplayed()
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_NO_PICTURES_TEXT)
.assertDisplayComponentInScroll()
}

@Test
fun testFullSizePictureOnClick() {
setEventScreen(events[0])
goToGallery()
composeTestRule.waitUntil(5000) {
composeTestRule
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[0].uid)
.isDisplayed()
}

composeTestRule
.onNodeWithTag(EventDetailsTestTags.USER_EVENT_PICTURE + eventPictures[0].uid)
.performClick()

composeTestRule.onNodeWithTag(EventDetailsTestTags.PICTURE_FULL_SCREEN).assertIsDisplayed()
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_LEFT)
.assertIsDisplayed()
composeTestRule
.onNodeWithTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_RIGHT)
.assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class EventCreationE2ETest : EndToEndTest() {
val dateToSelect = LocalDate.of(currentDate.year, currentDate.month, day)
val dateString = dateToSelect.format(dateFormatter)

composeTestRule.onNodeWithText(dateString).performClick()
composeTestRule.onNodeWithText(dateString, true).performClick()

composeTestRule
.onNodeWithText(context.getString(R.string.event_creation_dialog_ok))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ constructor(
event.eventPictures.add(newEventPicture.uid)
updateEventWithoutImage(
event,
{ event.eventPictures.requestAll(lazy = true) },
{ event.eventPictures.add(newEventPicture) },
{ e ->
Log.e("EventViewModel", "An error occurred while updating an event: $e")
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,7 @@ object EventDetailsTestTags {
const val EVENT_DETAILS_PAGER = "eventDetailsPager"
const val EVENT_NOT_STARTED_TEXT = "eventNotStartedText"
const val EVENT_NO_PICTURES_TEXT = "eventNoPicturesText"
const val EVENT_PICTURES_ARROW_LEFT = "picturesArrowLeft"
const val EVENT_PICTURES_ARROW_RIGHT = "picturesArrowRight"
const val PICTURE_FULL_SCREEN = "pictureFullScreen"
}
36 changes: 33 additions & 3 deletions app/src/main/java/com/android/unio/ui/event/EventDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -86,6 +88,7 @@ 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.event.overlay.PictureOverlay
import com.android.unio.ui.image.AsyncImageWrapper
import com.android.unio.ui.navigation.NavigationAction
import com.android.unio.ui.navigation.Screen
Expand Down Expand Up @@ -259,7 +262,8 @@ fun EventScreenContent(
imageUri = event.image.toUri(),
contentDescription = context.getString(R.string.event_image_description),
modifier = Modifier.fillMaxWidth().testTag(EventDetailsTestTags.DETAILS_IMAGE),
contentScale = ContentScale.Crop)
contentScale = ContentScale.Crop,
placeholderResourceId = R.drawable.placeholder_pictures)
}

EventInformationCard(event, organisers, context)
Expand Down Expand Up @@ -406,10 +410,16 @@ fun EventDetailsBody(
* The second page of the EventDetails horizontal scroll menu.
*
* @param event The event to display.
* @param context The local [Context]
*/
@Composable
fun EventDetailsPicturesTab(event: Event, context: Context) {
val eventPictures by event.eventPictures.list.collectAsState()
var showFullScreen by remember { mutableStateOf(false) }
var selectedPictureUri by remember { mutableStateOf(Uri.EMPTY) }
val pagerState = rememberPagerState { eventPictures.size }
val scope = rememberCoroutineScope()

if (event.startDate.seconds > Timestamp.now().seconds) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
Expand All @@ -433,13 +443,26 @@ fun EventDetailsPicturesTab(event: Event, context: Context) {
context.getString(R.string.event_details_user_picture_content_description),
filterQuality = FilterQuality.Medium,
placeholderResourceId = 0,
contentScale = ContentScale.Crop,
contentScale = ContentScale.Fit,
modifier =
Modifier.padding(3.dp)
.clip(RoundedCornerShape(10))
.testTag(EventDetailsTestTags.USER_EVENT_PICTURE + item.uid))
.testTag(EventDetailsTestTags.USER_EVENT_PICTURE + item.uid)
.clickable {
scope.launch { pagerState.scrollToPage(index) }
showFullScreen = true
})
}
}
if (showFullScreen) {
PictureOverlay(
onDismiss = {
selectedPictureUri = Uri.EMPTY
showFullScreen = false
},
pagerState,
eventPictures)
}
}
}
/**
Expand Down Expand Up @@ -553,6 +576,13 @@ fun EventDetailsBottomSheet(
}
}

/**
* Button that allows the user to save/unsave an event
*
* @param event the selected [Event].
* @param eventViewModel The [EventViewModel].
* @param userViewModel The [UserViewModel].
*/
@Composable
fun EventSaveButton(event: Event, eventViewModel: EventViewModel, userViewModel: UserViewModel) {
val context = LocalContext.current
Expand Down
111 changes: 111 additions & 0 deletions app/src/main/java/com/android/unio/ui/event/overlay/PictureOverlay.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.android.unio.ui.event.overlay

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import com.android.unio.R
import com.android.unio.model.event.EventUserPicture
import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags
import com.android.unio.model.strings.test_tags.event.EventDetailsTestTags.PICTURE_FULL_SCREEN
import com.android.unio.ui.image.AsyncImageWrapper
import kotlinx.coroutines.launch

/**
* A dialog that allows users to view event pictures in full screen.
*
* @param onDismiss Callback when the dialog is dismissed.
* @param pagerState the [PagerState] of the pager.
* @param eventPictures The list of [EventUserPicture] to display.
*/
@Composable
fun PictureOverlay(
onDismiss: () -> Unit,
pagerState: PagerState,
eventPictures: List<EventUserPicture>
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val iconSize = 40.dp
val onClickArrow: (Boolean) -> Unit = { isRight: Boolean ->
if (isRight && pagerState.currentPage < eventPictures.size - 1) {
scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) }
} else if (!isRight && pagerState.currentPage > 0) {
scope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) }
}
}
Dialog(
onDismissRequest = onDismiss,
properties =
DialogProperties(
dismissOnClickOutside = true,
dismissOnBackPress = true,
usePlatformDefaultWidth = false)) {
Box(
modifier = Modifier.testTag(PICTURE_FULL_SCREEN).fillMaxHeight(0.5f),
contentAlignment = Alignment.Center) {
HorizontalPager(pagerState, pageSpacing = 40.dp, beyondViewportPageCount = 1) { page
->
AsyncImageWrapper(
imageUri = eventPictures[page].image.toUri(),
contentDescription =
context.getString(
R.string.event_details_content_description_full_screen_picture),
filterQuality = FilterQuality.High,
placeholderResourceId = 0,
contentScale = ContentScale.Fit,
modifier = Modifier.padding(horizontal = 55.dp))
}
IconButton(
onClick = { onClickArrow(false) },
modifier =
Modifier.align(Alignment.CenterStart)
.testTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_LEFT),
colors =
IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.onPrimary,
containerColor = MaterialTheme.colorScheme.primary)) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
context.getString(R.string.event_details_content_description_arrow_left),
modifier = Modifier.size(iconSize))
}

IconButton(
onClick = { onClickArrow(true) },
modifier =
Modifier.align(Alignment.CenterEnd)
.testTag(EventDetailsTestTags.EVENT_PICTURES_ARROW_RIGHT),
colors =
IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.onPrimary,
containerColor = MaterialTheme.colorScheme.primary)) {
Icon(
Icons.AutoMirrored.Filled.ArrowForward,
context.getString(R.string.event_details_content_description_arrow_right),
modifier = Modifier.size(iconSize))
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,9 @@
<string name="event_details_user_picture_content_description">Une photo téléchargée par un utilisateur</string>
<string name="event_details_description_tab">Description</string>
<string name="event_details_pictures_tab">Photos de l\'évènement</string>
<string name="event_details_content_description_full_screen_picture">Image en plein écran</string>
<string name="event_details_content_description_arrow_left">Flèche gauche</string>
<string name="event_details_content_description_arrow_right">Flèche droite</string>

<!-- Bottom Navigation Strings -->
<string name="bottom_nav_home">Accueil</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@
<string name="event_details_user_picture_content_description">A picture uploaded by a user</string>
<string name="event_details_description_tab">Description</string>
<string name="event_details_pictures_tab">Event pictures</string>
<string name="event_details_content_description_full_screen_picture">Full Screen Picture</string>
<string name="event_details_content_description_arrow_left">Arrow Left</string>
<string name="event_details_content_description_arrow_right">Arrow Right</string>


/**
* package: explore
Expand Down

0 comments on commit 2c248c9

Please sign in to comment.