Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DIA-1015): infinite discovery swiping behavior #11292

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2703,7 +2703,7 @@ SPEC CHECKSUMS:
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
GTMSessionFetcher: 0e876eea9782ec6462e91ab872711c357322c94f
hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0
Interstellar: cd0c3290e249b5f156113a4c6c9c09dd0f418aaa
Interstellar: ab67502af03105f92100a043e178d188a1a437c9
INTUAnimationEngine: 3a7d63738cd51af573d16848a771feedea7cc9f2
iOSSnapshotTestCase: a670511f9ee3829c2b9c23e6e68f315fd7b6790f
ISO8601DateFormatter: 8311a2d4e265b269b2fed7ab4db685dcb0a7ccb2
Expand All @@ -2729,7 +2729,7 @@ SPEC CHECKSUMS:
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
Pulley: edc993fb57f7eb20541c8453d0fce10559f21dac
Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d
RCT-Folly: 34124ae2e667a0e5f0ea378db071d27548124321
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
RCTDeprecation: 726d24248aeab6d7180dac71a936bbca6a994ed1
RCTRequired: a94e7febda6db0345d207e854323c37e3a31d93b
RCTTypeSafety: 28e24a6e44f5cbf912c66dde6ab7e07d1059a205
Expand Down
12 changes: 9 additions & 3 deletions src/app/Components/FancySwiper/FancySwiper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ export const OFFSET_X = 100

interface FancySwiperProps {
cards: Card[]
onSwipeRight: () => void
hideActionButtons?: boolean
onSwipeLeft: () => void
onSwipeRight: () => void
}

export const FancySwiper = ({ cards, onSwipeRight, onSwipeLeft }: FancySwiperProps) => {
export const FancySwiper = ({
cards,
hideActionButtons = false,
onSwipeLeft,
onSwipeRight,
}: FancySwiperProps) => {
const remainingCards = cards.reverse()
const swiper = useRef<Animated.ValueXY>(new Animated.ValueXY()).current

Expand Down Expand Up @@ -84,7 +90,7 @@ export const FancySwiper = ({ cards, onSwipeRight, onSwipeLeft }: FancySwiperPro
)
})}
</Flex>
<FancySwiperIcons swiper={swiper} OnPress={onSwipeHandler} />
{!hideActionButtons && <FancySwiperIcons swiper={swiper} OnPress={onSwipeHandler} />}
</>
)
}
5 changes: 3 additions & 2 deletions src/app/Navigation/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { MakeOfferModalQueryRenderer } from "app/Scenes/Inbox/Components/Convers
import { PurchaseModalQueryRenderer } from "app/Scenes/Inbox/Components/Conversations/PurchaseModal"
import { ConversationQueryRenderer } from "app/Scenes/Inbox/Screens/Conversation"
import { ConversationDetailsQueryRenderer } from "app/Scenes/Inbox/Screens/ConversationDetails"
import { InfiniteDiscoveryView } from "app/Scenes/InfiniteDiscovery/InfiniteDiscovery"
import { MyAccountQueryRenderer } from "app/Scenes/MyAccount/MyAccount"
import { MyAccountDeleteAccountQueryRenderer } from "app/Scenes/MyAccount/MyAccountDeleteAccount"
import { MyAccountEditEmailQueryRenderer } from "app/Scenes/MyAccount/MyAccountEditEmail"
Expand Down Expand Up @@ -790,13 +791,13 @@ export const artsyDotNetRoutes = defineRoutes([
{
path: "/infinite-discovery",
name: "InfiniteDiscovery",
Component: InboxQueryRenderer,
Component: InfiniteDiscoveryView,
options: {
hidesBottomTabs: true,
screenOptions: {
headerShown: false,
},
},
Queries: [InboxScreenQuery],
},
{
path: "/inquiry/:artworkID",
Expand Down
173 changes: 115 additions & 58 deletions src/app/Scenes/InfiniteDiscovery/InfiniteDiscovery.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import {
Avatar,
Button,
Color,
Flex,
Image,
Screen,
Spacer,
Text,
Touchable,
useScreenDimensions,
useTheme,
} from "@artsy/palette-mobile"
import { InfiniteDiscoveryQuery } from "__generated__/InfiniteDiscoveryQuery.graphql"
import { FancySwiper } from "app/Components/FancySwiper/FancySwiper"
import { Card } from "app/Components/FancySwiper/FancySwiperCard"
import { InfiniteDiscoveryContext } from "app/Scenes/InfiniteDiscovery/InfiniteDiscoveryContext"
import { navigate } from "app/system/navigation/navigate"
import { extractNodes } from "app/utils/extractNodes"
import { NoFallback, withSuspense } from "app/utils/hooks/withSuspense"
import { graphql, useLazyLoadQuery } from "react-relay"

export const InfiniteDiscoveryView: React.FC = () => {
return (
<InfiniteDiscoveryContext.Provider>
<InfiniteDiscovery />
<InfiniteDiscoveryWithSuspense />
</InfiniteDiscoveryContext.Provider>
)
}

export const InfiniteDiscovery: React.FC = () => {
const artworks = InfiniteDiscoveryContext.useStoreState((state) => state.artworks)
const currentArtwork = InfiniteDiscoveryContext.useStoreState((state) => state.currentArtwork)
const goToPreviousArtwork = InfiniteDiscoveryContext.useStoreActions(
(action) => action.goToPreviousArtwork
)
const goToNextArtwork = InfiniteDiscoveryContext.useStoreActions(
(actions) => actions.goToNextArtwork
)
const { color } = useTheme()
const { width: screenWidth } = useScreenDimensions()

const currentIndex = InfiniteDiscoveryContext.useStoreState((state) => state.currentIndex)
const goToPrevious = InfiniteDiscoveryContext.useStoreActions((action) => action.goToPrevious)
const goToNext = InfiniteDiscoveryContext.useStoreActions((actions) => actions.goToNext)

const canGoBack = artworks.length && currentArtwork !== artworks[0]
const data = useLazyLoadQuery<InfiniteDiscoveryQuery>(infiniteDiscoveryQuery, {})

const artworks = extractNodes(data.marketingCollection?.artworksConnection)

const handleBackPressed = () => {
goToPreviousArtwork()
goToPrevious()
}

const handleExitPressed = () => {
Expand All @@ -45,71 +52,121 @@ export const InfiniteDiscovery: React.FC = () => {
}

const handleSwipedLeft = () => {
goToNextArtwork()
goToNext()
}

return (
<Screen>
<Screen.Header
title="Discovery"
leftElements={
<Touchable onPress={handleBackPressed}>
<Text variant="xs">Back</Text>
</Touchable>
}
hideLeftElements={!canGoBack}
rightElements={
<Touchable onPress={handleExitPressed}>
<Text variant="xs">Exit</Text>
</Touchable>
}
/>
<Screen.Body>
<FancySwiper
cards={artworkCards(artworks)}
onSwipeRight={handleSwipedRight}
onSwipeLeft={handleSwipedLeft}
/>
</Screen.Body>
</Screen>
)
}

const artworkCards = (artworks: Color[]) => {
return artworks.map((artwork) => {
const { color, space } = useTheme()
const { width } = useScreenDimensions()
const artworkCards: Card[] = artworks.map((artwork) => {
return {
jsx: (
<Flex width={width - space(4)} height={500} backgroundColor="white">
<Flex backgroundColor={color("white100")}>
<Flex flexDirection="row" justifyContent="space-between" testID="artist-header">
<Text variant="sm-display">Artist Name</Text>
<Button variant="outlineGray">Follow</Button>
<Flex flexDirection="row" px={2}>
<Avatar
initials={artwork?.artists?.[0]?.initials || undefined}
src={artwork?.artists?.[0]?.coverArtwork?.image?.cropped?.url || undefined}
size="xs"
/>
<Flex>
<Text variant="sm-display">{artwork.artistNames}</Text>
<Text variant="xs" color={color("black60")}>
{artwork?.artists?.[0]?.formattedNationalityAndBirthday}
</Text>
</Flex>
</Flex>
<Button variant="outlineGray" size="small">
Follow
</Button>
</Flex>
<Spacer y={2} />
<Flex alignItems="center">
{!!artwork?.images?.[0]?.url && (
<Image src={artwork.images[0].url} width={screenWidth} aspectRatio={0.79} />
)}
</Flex>
<Flex
backgroundColor={color(artwork)}
height={250}
testID="image-frame"
width={250}
></Flex>
<Flex testID="multi-image-tabs" />
<Flex flexDirection="row" justifyContent="space-between" testID="artwork-info">
<Flex>
<Flex flexDirection="row">
<Text color={color("black60")} italic variant="sm-display">
Artwork Title,
{artwork.title}
</Text>
<Text color={color("black60")} variant="sm-display">
2024
, {artwork.date}
</Text>
</Flex>
<Text variant="sm-display">$1,234</Text>
<Text variant="sm-display">{artwork.saleMessage}</Text>
</Flex>
<Button variant="fillGray">Save</Button>
</Flex>
</Flex>
),
id: artwork,
id: artwork.internalID,
}
})

return (
<Screen>
<Screen.Body fullwidth>
<Flex zIndex={-100}>
<Screen.Header
title="Discovery"
leftElements={
<Touchable onPress={handleBackPressed}>
<Text variant="xs">Back</Text>
</Touchable>
}
hideLeftElements={currentIndex === 0}
rightElements={
<Touchable onPress={handleExitPressed}>
<Text variant="xs">Exit</Text>
</Touchable>
}
/>
</Flex>
<FancySwiper
cards={artworkCards}
hideActionButtons
onSwipeRight={handleSwipedRight}
onSwipeLeft={handleSwipedLeft}
/>
</Screen.Body>
</Screen>
)
}

export const InfiniteDiscoveryWithSuspense = withSuspense({
Component: InfiniteDiscovery,
LoadingFallback: () => <Text>Loading...</Text>,
ErrorFallback: NoFallback,
})

export const infiniteDiscoveryQuery = graphql`
query InfiniteDiscoveryQuery {
marketingCollection(slug: "curators-picks") {
artworksConnection(first: 10) {
edges {
node {
artistNames
artists(shallow: true) {
coverArtwork {
image {
cropped(height: 45, width: 45) {
url
}
}
}
formattedNationalityAndBirthday
initials
}
date
internalID
images {
url(version: "large")
}
saleMessage
title
}
}
}
}
}
`
41 changes: 15 additions & 26 deletions src/app/Scenes/InfiniteDiscovery/InfiniteDiscoveryContext.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
import { Color } from "@artsy/palette-mobile"
import { action, Action, createContextStore } from "easy-peasy"

export interface InfiniteDiscoveryContextModel {
artworks: Color[]
currentArtwork: Color
goToPreviousArtwork: Action<this>
goToNextArtwork: Action<this>
count: number
currentIndex: number
goToPrevious: Action<this>
goToNext: Action<this>
}

export const initialModel: InfiniteDiscoveryContextModel = {
artworks: [
"black100",
"white100",
"green100",
"yellow100",
"orange100",
"red100",
"purple100",
"blue100",
],
currentArtwork: "black100",
goToPreviousArtwork: action((state) => {
const currentIndex = state.artworks.indexOf(state.currentArtwork)
if (currentIndex - 1 < 0) return
const previousIndex = currentIndex - 1
state.currentArtwork = state.artworks[previousIndex]
// TODO: this needs to come from the result of the query
count: 10,
currentIndex: 0,
goToPrevious: action((state) => {
if (state.currentIndex > 0) {
state.currentIndex = state.currentIndex - 1
}
}),
goToNextArtwork: action((state) => {
const currentIndex = state.artworks.indexOf(state.currentArtwork)
if (currentIndex + 1 === state.artworks.length) return
const nextIndex = currentIndex + 1
state.currentArtwork = state.artworks[nextIndex]
goToNext: action((state) => {
if (state.currentIndex < state.count - 1) {
state.currentIndex = state.currentIndex + 1
}
}),
}

Expand Down
Loading