Skip to content

Commit

Permalink
(PC-32301) feat(XPCine): add next movie feature (#7040)
Browse files Browse the repository at this point in the history
* feat: add next movie feature

* fix: add review feedbacks
  • Loading branch information
xlecunff-pass authored Oct 18, 2024
1 parent d617309 commit b845704
Show file tree
Hide file tree
Showing 23 changed files with 686 additions and 349 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, {
createContext,
useCallback,
useContext,
useMemo,
useRef,
useEffect,
useState,
PropsWithChildren,
} from 'react'
import { Animated, View, ViewStyle } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { Easing } from 'react-native-reanimated'
import styled from 'styled-components/native'

import { MovieCalendar } from 'features/offer/components/MovieCalendar/MovieCalendar'
import { handleMovieCalendarScroll } from 'features/offer/components/MoviesScreeningCalendar/utils'
import { useNextDays } from 'features/offer/helpers/useNextDays/useNextDays'
import { Anchor } from 'ui/components/anchor/Anchor'
import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
import { useLayout } from 'ui/hooks/useLayout'

type MovieCalendarContextType = {
selectedDate: Date
goToDate: (date: Date) => void
}

const MovieCalendarContext = createContext<MovieCalendarContextType | undefined>(undefined)

const AnimatedCalendarView: React.FC<PropsWithChildren<{ selectedDate: Date }>> = ({
selectedDate,
children,
}) => {
const fadeAnim = useRef(new Animated.Value(0)).current
const translateAnim = useRef(new Animated.Value(0)).current
const [width, setWidth] = useState<number>(0)

useEffect(() => {
translateAnim.setValue(0)
fadeAnim.setValue(0)
Animated.timing(translateAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
easing: Easing.out(Easing.ease),
}).start()
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
easing: Easing.in(Easing.ease),
useNativeDriver: true,
}).start()
}, [fadeAnim, translateAnim, selectedDate])

return (
<AnimationContainer>
<Animated.View
onLayout={({ nativeEvent }) => {
setWidth(nativeEvent.layout.width)
}}
style={{
opacity: fadeAnim,
transform: [
{ translateX: Animated.subtract(Animated.multiply(translateAnim, width), width) },
],
}}>
{children}
</Animated.View>
</AnimationContainer>
)
}

export const MovieCalendarProvider: React.FC<{
nbOfDays: number
children: React.ReactNode
containerStyle?: ViewStyle
}> = ({ nbOfDays, containerStyle, children }) => {
const { dates, selectedDate, setSelectedDate } = useNextDays(nbOfDays)
const flatListRef = useRef<FlatList | null>(null)
const { width: flatListWidth, onLayout: onFlatListLayout } = useLayout()
const { width: itemWidth, onLayout: onItemLayout } = useLayout()
const scrollToAnchor = useScrollToAnchor()

const scrollToMiddleElement = useCallback(
(currentIndex: number) => {
const { offset } = handleMovieCalendarScroll(currentIndex, flatListWidth, itemWidth)

flatListRef.current?.scrollToOffset({
animated: true,
offset,
})
},
[flatListRef, flatListWidth, itemWidth]
)

useEffect(() => {
const currentIndex = dates.findIndex(
(date) => (date as Date).toDateString() === selectedDate.toDateString()
)

scrollToMiddleElement(currentIndex)
}, [selectedDate, dates, scrollToMiddleElement])

useEffect(() => {
if (flatListRef?.current) {
flatListRef.current?.scrollToOffset({ offset: 0 })
}
}, [flatListRef])

const goToDate = useCallback(
(date: Date) => {
scrollToAnchor('movie-calendar')
setSelectedDate(date)
},
[scrollToAnchor, setSelectedDate]
)

const value = useMemo(() => ({ selectedDate, goToDate }), [selectedDate, goToDate])

return (
<MovieCalendarContext.Provider value={value}>
<View style={containerStyle}>
<Anchor name="movie-calendar">
<MovieCalendar
dates={dates}
selectedDate={selectedDate}
onTabChange={setSelectedDate}
flatListRef={flatListRef}
flatListWidth={flatListWidth}
onFlatListLayout={onFlatListLayout}
itemWidth={itemWidth}
onItemLayout={onItemLayout}
/>
</Anchor>
</View>
<AnimatedCalendarView selectedDate={selectedDate}>{children}</AnimatedCalendarView>
</MovieCalendarContext.Provider>
)
}

const AnimationContainer = styled.View({ overflow: 'hidden' })

export const useMovieCalendar = (): MovieCalendarContextType => {
const context = useContext(MovieCalendarContext)
if (context === undefined) {
throw new Error('useMovieCalendar must be used within a MovieCalendarProvider')
}
return context
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC, useCallback, useMemo } from 'react'
import { FlatList, View } from 'react-native'
import React, { FC, useMemo } from 'react'
import { View } from 'react-native'
import styled from 'styled-components/native'

import type { OfferPreviewResponse } from 'api/gen'
Expand All @@ -9,48 +9,35 @@ import {
} from 'features/offer/components/MovieScreeningCalendar/useMovieScreeningCalendar'
import { useSelectedDateScreening } from 'features/offer/components/MovieScreeningCalendar/useSelectedDateScreenings'
import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
import { useMovieCalendar } from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
import { NextScreeningButton } from 'features/offer/components/MoviesScreeningCalendar/NextScreeningButton'
import { handleMovieCalendarScroll } from 'features/offer/components/MoviesScreeningCalendar/utils'
import { useOfferCTAButton } from 'features/offer/components/OfferCTAButton/useOfferCTAButton'
import { formatDuration } from 'features/offer/helpers/formatDuration/formatDuration'
import { VenueOffers } from 'features/venue/api/useVenueOffers'
import { useSubcategoriesMapping } from 'libs/subcategories'
import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
import { EventCardList } from 'ui/components/eventCard/EventCardList'
import { HorizontalOfferTile } from 'ui/components/tiles/HorizontalOfferTile'
import { Spacer } from 'ui/theme'

type MovieOfferTileProps = {
movieOffer: MovieOffer
venueOffers: VenueOffers
date: Date
isLast: boolean
nextScreeningDate?: Date
setSelectedDate: (date: Date) => void
nextDateIndex: number
flatListRef: React.MutableRefObject<FlatList | null>
flatListWidth: number
itemWidth: number
}

export const MovieOfferTile: FC<MovieOfferTileProps> = ({
venueOffers,
date,
movieOffer: { offer },
isLast,
nextScreeningDate,
setSelectedDate,
nextDateIndex,
flatListRef,
flatListWidth,
itemWidth,
}) => {
const movieScreenings = getMovieScreenings(offer.stocks)
const scrollToAnchor = useScrollToAnchor()
const { goToDate, selectedDate } = useMovieCalendar()

const selectedScreeningStock = useMemo(
() => movieScreenings[getDateString(String(date))],
[movieScreenings, date]
() => movieScreenings[getDateString(String(selectedDate))],
[movieScreenings, selectedDate]
)

const subcategoriesMapping = useSubcategoriesMapping()
Expand All @@ -60,18 +47,6 @@ export const MovieOfferTile: FC<MovieOfferTileProps> = ({
offer.isExternalBookingsDisabled
)

const scrollToMiddleElement = useCallback(
(currentIndex: number) => {
const { offset } = handleMovieCalendarScroll(currentIndex, flatListWidth, itemWidth)

flatListRef.current?.scrollToOffset({
animated: true,
offset,
})
},
[flatListRef, flatListWidth, itemWidth]
)

const {
onPress: onPressOfferCTA,
CTAOfferModal,
Expand Down Expand Up @@ -106,11 +81,7 @@ export const MovieOfferTile: FC<MovieOfferTileProps> = ({
<View>
<NextScreeningButton
date={nextScreeningDate}
onPress={() => {
scrollToAnchor('venue-calendar')
setSelectedDate(nextScreeningDate)
scrollToMiddleElement(nextDateIndex)
}}
onPress={() => goToDate(nextScreeningDate)}
/>
</View>
) : (
Expand Down
Loading

0 comments on commit b845704

Please sign in to comment.