Skip to content

Commit

Permalink
LMN-965 Add image gallery slideshow (#1839)
Browse files Browse the repository at this point in the history
* LMN-965 Add image gallery slideshow

* Updated snapshots for 'rtl'

* Updated snapshots for 'default'

* LMN-965 Change API to initial image

* Updated snapshots for 'dm'

* Trigger CI

* Updated snapshots for 'dm'

* Trigger CI

* LMN-965 Fix bottom padding for carousel badge

* Updated snapshots for 'rtl'

* Updated snapshots for 'dm'

* Updated snapshots for 'default'

* Trigger CI

* Updated snapshots for 'rtl'

* LMN-965 Reset orientation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Maria Neumayer and github-actions[bot] authored Jan 5, 2024
1 parent 832f5ff commit 0c2b1b9
Show file tree
Hide file tree
Showing 21 changed files with 485 additions and 50 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ dependencies {
androidTestImplementation libs.test.facebookSnapshotTestingCore
androidTestImplementation libs.ktor.clientCore
androidTestImplementation libs.ktor.clientCio
androidTestImplementation libs.test.uiautomator
implementation project(':Backpack')
implementation project(':backpack-compose')
implementation project(":meta:annotations")
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@

package net.skyscanner.backpack.compose.imagegallery

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.isDialog
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import net.skyscanner.backpack.compose.BpkSnapshotTest
import net.skyscanner.backpack.demo.compose.ImageGalleryCarouselStory
import net.skyscanner.backpack.demo.compose.ImageGallerySlideshowStory
import org.junit.Test

class BpkImageGalleryTest : BpkSnapshotTest() {
Expand All @@ -28,4 +33,30 @@ class BpkImageGalleryTest : BpkSnapshotTest() {
fun carousel() {
snap { ImageGalleryCarouselStory() }
}

@Test
fun slideshow() {
recordModal { ImageGallerySlideshowStory() }
}

@Test
fun slideshow_landscape() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.setOrientationLeft()
recordModal { ImageGallerySlideshowStory() }
device.setOrientationNatural()
}

@Test
fun slideshow_second_page() {
recordModal { ImageGallerySlideshowStory(initialPage = 2) }
}

private fun recordModal(content: @Composable () -> Unit) {
snap(comparison = { name ->
compareScreenshot(onNode(isDialog()), name)
}) {
content()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,26 @@
package net.skyscanner.backpack.demo.compose

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import net.skyscanner.backpack.compose.button.BpkButton
import net.skyscanner.backpack.compose.carousel.rememberBpkCarouselState
import net.skyscanner.backpack.compose.imagegallery.BpkImageGallery
import net.skyscanner.backpack.compose.imagegallery.BpkImageGalleryCarousel
import net.skyscanner.backpack.compose.imagegallery.BpkImageGalleryImage
import net.skyscanner.backpack.compose.modal.rememberBpkModalState
import net.skyscanner.backpack.demo.R
import net.skyscanner.backpack.demo.components.ImageGalleryComponent
import net.skyscanner.backpack.demo.meta.ComposeStory
Expand Down Expand Up @@ -62,3 +73,78 @@ fun ImageGalleryCarouselStory(
)
}
}

@Composable
@ImageGalleryComponent
@ComposeStory(name = "Slideshow")
fun ImageGallerySlideshowStory(
modifier: Modifier = Modifier,
initialPage: Int = 0,
) {
val showModal = rememberSaveable { mutableStateOf(true) }

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
BpkButton(
text = stringResource(R.string.generic_show),
onClick = { showModal.value = true },
)
}

if (showModal.value) {
val modalState = rememberBpkModalState()
val coroutineScope = rememberCoroutineScope()

BpkImageGallery(
modifier = modifier,
state = modalState,
closeContentDescription = stringResource(R.string.navigation_close),
initialImage = initialPage,
onCloseClicked = { coroutineScope.launch { modalState.hide() } },
onDismiss = { showModal.value = false },
images = listOf(
BpkImageGalleryImage(
title = stringResource(R.string.image_gallery_title_1),
description = stringResource(R.string.image_gallery_description_1),
credit = stringResource(R.string.image_gallery_photographer_1),
content = { contentDescription ->
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(R.drawable.carousel_placeholder_1),
contentDescription = contentDescription,
)
},
),
BpkImageGalleryImage(
title = stringResource(R.string.image_gallery_title_x, 2),
content = { contentDescription ->
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(R.drawable.carousel_placeholder_2),
contentDescription = contentDescription,
)
},
),
BpkImageGalleryImage(
title = stringResource(R.string.image_gallery_title_x, 3),
content = { contentDescription ->
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(R.drawable.carousel_placeholder_3),
contentDescription = contentDescription,
)
},
),
BpkImageGalleryImage(
title = stringResource(R.string.image_gallery_title_x, 4),
content = { contentDescription ->
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(R.drawable.carousel_placeholder_4),
contentDescription = contentDescription,
)
},
),
),
)
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,9 @@
<string name="inset_banner_body">You can change your destination, date of travel, or both, with no change fee. Valid for all new bookings made up to 31 May for travel between now and 31 December 2020.</string>
<string name="text_field_clear_action_description">Clear</string>
<string name="with_clear_action_title">With clear action</string>

<string name="image_gallery_title_1">Pumphouse Point</string>
<string name="image_gallery_description_1">Walk deep into the fjord-like surrounds of Lake St Clair, explore the giant myrtle forests, tread softly on the moss-covered understory and forget the world you left behind</string>
<string name="image_gallery_photographer_1">\@PhotographerName</string>
<string name="image_gallery_title_x">Image %d</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ fun BpkCarousel(
@Composable
internal fun BpkCarousel(
state: BpkCarouselState,
content: @Composable (BoxScope.(Int) -> Unit),
overlayContent: @Composable (BoxScope.((@Composable () -> Unit)?) -> Unit),
modifier: Modifier = Modifier,
content: @Composable (BoxScope.(Int) -> Unit),
) {
val internalState = state.asInternalState()
Box(modifier = modifier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,42 @@

package net.skyscanner.backpack.compose.imagegallery

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import net.skyscanner.backpack.compose.badge.BpkBadge
import net.skyscanner.backpack.compose.badge.BpkBadgeType
import net.skyscanner.backpack.compose.carousel.BpkCarousel
import net.skyscanner.backpack.compose.carousel.BpkCarouselState
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.imagegallery.internal.BpkImageGallerySlideshow
import net.skyscanner.backpack.compose.modal.BpkModal
import net.skyscanner.backpack.compose.modal.BpkModalState
import net.skyscanner.backpack.compose.modal.rememberBpkModalState
import net.skyscanner.backpack.compose.navigationbar.NavIcon

@Composable
fun BpkImageGalleryCarousel(
state: BpkCarouselState,
fun BpkImageGallery(
images: List<BpkImageGalleryImage>,
initialImage: Int,
closeContentDescription: String,
onCloseClicked: () -> Unit,
onDismiss: (() -> Unit),
modifier: Modifier = Modifier,
onImageClicked: ((Int) -> Unit)? = null,
content: @Composable (BoxScope.(Int) -> Unit),
onImageChanged: ((Int) -> Unit)? = null,
state: BpkModalState = rememberBpkModalState(),
) {
val interactionSource = remember { MutableInteractionSource() }

BpkCarousel(
modifier = modifier.clickable(interactionSource = interactionSource, indication = null) {
onImageClicked?.invoke(
state.currentPage,
)
},
BpkModal(
navIcon = NavIcon.Close(closeContentDescription, onCloseClicked),
onDismiss = onDismiss,
state = state,
content = content,
overlayContent = { pageIndicator ->
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(
horizontal = BpkSpacing.Base,
vertical = ImageGalleryPreviewVerticalSpacing,
),
) {
pageIndicator?.invoke()
BpkBadge(
modifier = Modifier
.align(Alignment.BottomEnd),
text = "${state.currentPage + 1}/${state.pageCount}",
type = BpkBadgeType.Inverse,
)
}
},
)
modifier = modifier,
) {
BpkImageGallerySlideshow(
images = images,
initialImage = initialImage,
onImageChanged = onImageChanged,
)
}
}

private val ImageGalleryPreviewVerticalSpacing = 48.dp
data class BpkImageGalleryImage(
val title: String,
val description: String? = null,
val credit: String? = null,
val content: @Composable (contentDescription: String) -> Unit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.skyscanner.backpack.compose.imagegallery

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import net.skyscanner.backpack.compose.badge.BpkBadge
import net.skyscanner.backpack.compose.badge.BpkBadgeType
import net.skyscanner.backpack.compose.carousel.BpkCarousel
import net.skyscanner.backpack.compose.carousel.BpkCarouselState
import net.skyscanner.backpack.compose.tokens.BpkSpacing

@Composable
fun BpkImageGalleryCarousel(
state: BpkCarouselState,
modifier: Modifier = Modifier,
onImageClicked: ((Int) -> Unit)? = null,
content: @Composable (BoxScope.(Int) -> Unit),
) {
val interactionSource = remember { MutableInteractionSource() }

BpkCarousel(
modifier = modifier.clickable(interactionSource = interactionSource, indication = null) {
onImageClicked?.invoke(
state.currentPage,
)
},
state = state,
content = content,
overlayContent = { pageIndicator ->
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(
horizontal = BpkSpacing.Base,
vertical = ImageGalleryPreviewVerticalSpacing,
),
) {
pageIndicator?.invoke()
BpkBadge(
modifier = Modifier
.align(Alignment.BottomEnd),
text = "${state.currentPage + 1}/${state.pageCount}",
type = BpkBadgeType.Inverse,
)
}
},
)
}

private val ImageGalleryPreviewVerticalSpacing = 48.dp
Loading

0 comments on commit 0c2b1b9

Please sign in to comment.