Skip to content

Commit

Permalink
(PC-31854) feat(venue): add video section + fake video player component
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathieu Meissonnier committed Sep 20, 2024
1 parent a331434 commit 63572e1
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 2 deletions.
6 changes: 6 additions & 0 deletions src/features/venue/components/VenueContent/VenueContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { VenueCTA } from 'features/venue/components/VenueCTA/VenueCTA'
import { VenueHeader } from 'features/venue/components/VenueHeader/VenueHeader'
import { VenueTopComponent } from 'features/venue/components/VenueTopComponent/VenueTopComponent'
import { VenueWebMetaHeader } from 'features/venue/components/VenueWebMetaHeader'
import { VideoSection } from 'features/venue/components/VideoSection/VideoSection'
import { isCloseToBottom } from 'libs/analytics'
import { useFunctionOnce } from 'libs/hooks'
import { BatchEvent, BatchUser } from 'libs/react-native-batch'
Expand All @@ -23,6 +24,7 @@ type Props = {
venue: VenueResponse
gtlPlaylists?: GtlPlaylistData[]
venueOffers?: VenueOffers
videoSectionVisible?: boolean
}

const trackEventHasSeenVenueForSurvey = () => BatchUser.trackEvent(BatchEvent.hasSeenVenueForSurvey)
Expand All @@ -32,6 +34,7 @@ export const VenueContent: React.FunctionComponent<Props> = ({
venue,
gtlPlaylists,
venueOffers,
videoSectionVisible,
}) => {
const triggerBatch = useFunctionOnce(trackEventHasSeenVenueForSurvey)
const scrollViewRef = useRef<ScrollView>(null)
Expand Down Expand Up @@ -86,6 +89,9 @@ export const VenueContent: React.FunctionComponent<Props> = ({
{isLargeScreen ? <Placeholder height={headerHeight} /> : null}
<VenueTopComponent venue={venue} />
<Spacer.Column numberOfSpaces={isDesktopViewport ? 10 : 6} />
{videoSectionVisible ? (
<VideoSection venueType={venue.venueTypeCode} onPress={() => false} />
) : null}
<VenueBody
venue={venue}
playlists={gtlPlaylists}
Expand Down
64 changes: 64 additions & 0 deletions src/features/venue/components/VideoSection/FakeVideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { FunctionComponent } from 'react'
import { View } from 'react-native'
// eslint-disable-next-line no-restricted-imports
import FastImage, { Source } from 'react-native-fast-image'
import styled from 'styled-components/native'

import { TouchableOpacity } from 'ui/components/TouchableOpacity'
import { Play } from 'ui/svg/icons/Play'
import { getSpacing } from 'ui/theme'

type FakeVideoPlayerProps = {
imageSource: number | Source
width: number
height: number
onPress: () => void
}

export const FakeVideoPlayer: FunctionComponent<FakeVideoPlayerProps> = ({
imageSource,
width,
height,
onPress,
}) => {
return (
<TouchableOpacity onPress={onPress}>
<Container height={height} width={width}>
<PlayIconWrapper shouldRasterizeIOS>
<PlayIcon />
</PlayIconWrapper>
<BackgroundImage source={imageSource} resizeMode="cover" />
</Container>
</TouchableOpacity>
)
}

const Container = styled.View<Pick<FakeVideoPlayerProps, 'height' | 'width'>>(
({ theme, width, height }) => ({
width,
height,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.colors.black,
})
)

const PlayIconWrapper = styled(View)({
position: 'absolute',
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
zIndex: 2,
})

const BackgroundImage = styled(FastImage)({
width: '100%',
height: '100%',
opacity: 0.4,
})

const PlayIcon = styled(Play).attrs(({ theme }) => ({
size: getSpacing(24),
color: theme.colors.brownLight,
}))``
49 changes: 49 additions & 0 deletions src/features/venue/components/VideoSection/VideoSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useRef } from 'react'
import { useWindowDimensions } from 'react-native'
import { useTheme } from 'styled-components'
import styled from 'styled-components/native'

import { VenueTypeCodeKey } from 'api/gen'
import {
getVideoPlayerDimensions,
RATIO169,
} from 'features/home/components/helpers/getVideoPlayerDimensions'
import { FakeVideoPlayer } from 'features/venue/components/VideoSection/FakeVideoPlayer'
import { getFakeVideoBackground } from 'features/venue/helpers/getFakeVideoBackground'
import { SectionWithDivider } from 'ui/components/SectionWithDivider'
import { getSpacing, Spacer, Typo } from 'ui/theme'
import { getHeadingAttrs } from 'ui/theme/typographyAttrs/getHeadingAttrs'

type VideoSectionProps = {
onPress: () => void
venueType?: VenueTypeCodeKey | null
}

export const VideoSection = ({ venueType, onPress }: VideoSectionProps) => {
const { isDesktopViewport } = useTheme()
const { width: windowWidth } = useWindowDimensions()
const { playerHeight, playerWidth } = getVideoPlayerDimensions(
isDesktopViewport,
windowWidth,
RATIO169
)

const fakeVideoBackgroundSource = useRef(getFakeVideoBackground(venueType)).current

return (
<SectionWithDivider visible gap={6}>
<Title />
<FakeVideoPlayer
imageSource={fakeVideoBackgroundSource}
height={playerHeight}
width={playerWidth}
onPress={onPress}
/>
<Spacer.Column numberOfSpaces={2} />
</SectionWithDivider>
)
}

const Title = styled(Typo.Title3).attrs({ ...getHeadingAttrs(2), children: 'Vidéo de ce lieu' })({
marginLeft: getSpacing(6),
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Source } from 'react-native-fast-image'

type VideoSectionProps = {
title: string
source: number | Source
onPress: () => void
}

export const VideoSection = (_props: VideoSectionProps) => null
34 changes: 34 additions & 0 deletions src/features/venue/helpers/getFakeVideoBackground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { VenueTypeCodeKey } from 'api/gen'
import bookstore from 'ui/images/bg-bookstore.jpeg'
import concert from 'ui/images/bg-concert.jpeg'
import movie from 'ui/images/bg-movie.jpeg'
import museum from 'ui/images/bg-museum.jpeg'
import other from 'ui/images/bg-other.jpeg'
import store from 'ui/images/bg-store.jpeg'

export const getFakeVideoBackground = (type?: VenueTypeCodeKey | null) => {
switch (type) {
case VenueTypeCodeKey.BOOKSTORE:
case VenueTypeCodeKey.LIBRARY:
return bookstore
case VenueTypeCodeKey.MOVIE:
case VenueTypeCodeKey.TRAVELING_CINEMA:
return movie
case VenueTypeCodeKey.FESTIVAL:
case VenueTypeCodeKey.CONCERT_HALL:
case VenueTypeCodeKey.PERFORMING_ARTS:
return concert
case VenueTypeCodeKey.RECORD_STORE:
case VenueTypeCodeKey.DISTRIBUTION_STORE:
case VenueTypeCodeKey.CREATIVE_ARTS_STORE:
case VenueTypeCodeKey.MUSICAL_INSTRUMENT_STORE:
return store
case VenueTypeCodeKey.VISUAL_ARTS:
case VenueTypeCodeKey.CULTURAL_CENTRE:
case VenueTypeCodeKey.MUSEUM:
case VenueTypeCodeKey.PATRIMONY_TOURISM:
return museum
default:
return other
}
}
15 changes: 14 additions & 1 deletion src/features/venue/pages/Venue/Venue.native.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import { venueDataTest } from 'features/venue/fixtures/venueDataTest'
import { Venue } from 'features/venue/pages/Venue/Venue'
import { analytics } from 'libs/analytics'
import * as useFeatureFlag from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
import { Offer } from 'shared/offer/types'
import { mockServer } from 'tests/mswServer'
import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
import { fireEvent, render, screen, waitFor } from 'tests/utils'

jest.spyOn(useFeatureFlag, 'useFeatureFlag').mockReturnValue(false)
const useFeatureFlagSpy = jest.spyOn(useFeatureFlag, 'useFeatureFlag').mockReturnValue(false)

const activateFeatureFlags = (activeFeatureFlags: RemoteStoreFeatureFlags[] = []) => {
useFeatureFlagSpy.mockImplementation((flag) => activeFeatureFlags.includes(flag))
}

jest.useFakeTimers()

Expand Down Expand Up @@ -95,6 +100,7 @@ jest.mock('@batch.com/react-native-plugin', () =>

describe('<Venue />', () => {
beforeEach(() => {
activateFeatureFlags()
mockServer.getApi<VenueResponse>(`/v1/venue/${venueId}`, venueDataTest)
})

Expand All @@ -114,6 +120,13 @@ describe('<Venue />', () => {
expect(screen).toMatchSnapshot()
})

it('should display video section with FF on', async () => {
activateFeatureFlags([RemoteStoreFeatureFlags.WIP_FAKEDOOR_VIDEO_VENUE])
renderVenue(venueId)

expect(await screen.findByText('Vidéo de ce lieu')).toBeOnTheScreen()
})

describe('analytics', () => {
it.each([['deeplink'], ['venueMap']])(
'should log consult venue when URL from param equal to %s',
Expand Down
12 changes: 11 additions & 1 deletion src/features/venue/pages/Venue/Venue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { useVenue } from 'features/venue/api/useVenue'
import { useVenueOffers } from 'features/venue/api/useVenueOffers'
import { VenueContent } from 'features/venue/components/VenueContent/VenueContent'
import { analytics } from 'libs/analytics'
import { useFeatureFlag } from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'

export const Venue: FunctionComponent = () => {
const { params } = useRoute<UseRouteType<'Venue'>>()
const { data: venue } = useVenue(params.id)
const { gtlPlaylists } = useGTLPlaylists({ venue, queryKey: 'VENUE_GTL_PLAYLISTS' })
const { data: venueOffers } = useVenueOffers(venue)
const videoSectionVisible = useFeatureFlag(RemoteStoreFeatureFlags.WIP_FAKEDOOR_VIDEO_VENUE)

useEffect(() => {
if ((params.from === 'deeplink' || params.from === 'venueMap') && venue?.id) {
Expand All @@ -22,5 +25,12 @@ export const Venue: FunctionComponent = () => {

if (!venue) return null

return <VenueContent venue={venue} gtlPlaylists={gtlPlaylists} venueOffers={venueOffers} />
return (
<VenueContent
venue={venue}
gtlPlaylists={gtlPlaylists}
venueOffers={venueOffers}
videoSectionVisible={videoSectionVisible}
/>
)
}
9 changes: 9 additions & 0 deletions src/image.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare module '*.png' {
const value: any

Check warning on line 2 in src/image.d.ts

View workflow job for this annotation

GitHub Actions / yarn-linter / yarn_lint

Unexpected any. Specify a different type
export = value
}

declare module '*.jpeg' {
const value: any

Check warning on line 7 in src/image.d.ts

View workflow job for this annotation

GitHub Actions / yarn-linter / yarn_lint

Unexpected any. Specify a different type
export = value
}
1 change: 1 addition & 0 deletions src/libs/firebase/firestore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ export enum RemoteStoreFeatureFlags {
WIP_NEW_EXCLUSIVITY_MODULE = 'wipNewExclusivityModule',
TARGET_XP_CINE_FROM_OFFER = 'targetXpCineFromOffer',
WIP_ARTIST_PAGE = 'wipArtistPage',
WIP_FAKEDOOR_VIDEO_VENUE = 'wipFakeDoorVideoVenue',
}
Binary file added src/ui/images/bg-bookstore.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ui/images/bg-concert.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ui/images/bg-movie.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ui/images/bg-museum.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ui/images/bg-other.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ui/images/bg-store.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 63572e1

Please sign in to comment.