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: Long-press artwork context menu for Android #11359

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
54 changes: 27 additions & 27 deletions src/app/Components/ArtworkRail/ArtworkRailCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,30 @@ export const ArtworkRailCard: React.FC<ArtworkRailCardProps> = ({
contextScreenOwnerSlug={contextScreenOwnerSlug}
contextScreenOwnerType={contextScreenOwnerType}
>
<ContextMenuArtwork
contextModule={contextModule}
contextScreenOwnerType={contextScreenOwnerType}
onCreateAlertActionPress={() => setShowCreateArtworkAlertModal(true)}
onSupressArtwork={supressArtwork}
artwork={artwork}
artworkDisplayProps={{
dark,
showPartnerName,
hideArtistName,
lotLabel,
SalePriceComponent,
}}
>
<Box pr={2}>
<RouterLink
to={href || artwork.href}
underlayColor={backgroundColor}
activeOpacity={0.8}
onPress={onPress}
// To prevent navigation when opening the long-press context menu, `onLongPress` & `delayLongPress` need to be set (https://github.com/mpiannucci/react-native-context-menu-view/issues/60)
onLongPress={() => {}}
delayLongPress={400}
testID={testID}
<Box pr={2}>
<RouterLink
Comment on lines +95 to +96
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving ContextMenuArtwork inside RouterLink so RouterLink's onPress still works.

to={href || artwork.href}
underlayColor={backgroundColor}
activeOpacity={0.8}
onPress={onPress}
// To prevent navigation when opening the long-press context menu, `onLongPress` & `delayLongPress` need to be set (https://github.com/mpiannucci/react-native-context-menu-view/issues/60)
onLongPress={() => {}}
delayLongPress={400}
testID={testID}
>
<ContextMenuArtwork
contextModule={contextModule}
contextScreenOwnerType={contextScreenOwnerType}
onCreateAlertActionPress={() => setShowCreateArtworkAlertModal(true)}
onSupressArtwork={supressArtwork}
artwork={artwork}
artworkDisplayProps={{
dark,
showPartnerName,
hideArtistName,
lotLabel,
SalePriceComponent,
}}
>
<Flex
height={
Expand Down Expand Up @@ -161,9 +161,9 @@ export const ArtworkRailCard: React.FC<ArtworkRailCardProps> = ({
contextScreenOwnerType={contextScreenOwnerType}
/>
</Flex>
</RouterLink>
</Box>
</ContextMenuArtwork>
</ContextMenuArtwork>
</RouterLink>
</Box>

<CreateArtworkAlertModal
artwork={artwork}
Expand Down
127 changes: 89 additions & 38 deletions src/app/Components/ContextMenu/ContextMenuArtwork.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ActionType, ContextModule, LongPressedArtwork, ScreenOwnerType } from "@artsy/cohesion"
import { useColor } from "@artsy/palette-mobile"
import { Box, Flex, Join, Separator, Text, Touchable, useColor } from "@artsy/palette-mobile"
import { ContextMenuArtworkPreviewCard_artwork$key } from "__generated__/ContextMenuArtworkPreviewCard_artwork.graphql"
import { ContextMenuArtwork_artwork$key } from "__generated__/ContextMenuArtwork_artwork.graphql"
import { useSaveArtworkToArtworkLists } from "app/Components/ArtworkLists/useSaveArtworkToArtworkLists"
import { ArtworkRailCardProps } from "app/Components/ArtworkRail/ArtworkRailCard"
import { AutoHeightBottomSheet } from "app/Components/BottomSheet/AutoHeightBottomSheet"
import { ContextMenuArtworkPreviewCard } from "app/Components/ContextMenu/ContextMenuArtworkPreviewCard"
import { useShareSheet } from "app/Components/ShareSheet/ShareSheetContext"
import { LegacyNativeModules } from "app/NativeModules/LegacyNativeModules"
Expand All @@ -12,8 +13,10 @@ import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { useDislikeArtwork } from "app/utils/mutations/useDislikeArtwork"
import { Schema } from "app/utils/track"
import { isEmpty } from "lodash"
import { useState } from "react"
import { InteractionManager, Platform } from "react-native"
import ContextMenu, { ContextMenuAction, ContextMenuProps } from "react-native-context-menu-view"
import { TouchableHighlight } from "react-native-gesture-handler"
import { HapticFeedbackTypes, trigger } from "react-native-haptic-feedback"
import { graphql, useFragment } from "react-relay"
import { useTracking } from "react-tracking"
Expand All @@ -38,36 +41,6 @@ interface ContextMenuArtworkProps {
hideCreateAlertOnArtworkPreview?: boolean
}

const artworkFragment = graphql`
fragment ContextMenuArtwork_artwork on Artwork @argumentDefinitions(width: { type: "Int" }) {
...ContextMenuArtworkPreviewCard_artwork @arguments(width: $width)
...useSaveArtworkToArtworkLists_artwork

title
href
artistNames
artists(shallow: true) {
name
}
slug
internalID
id
isHangable
contextMenuImage: image {
url(version: ["larger", "large", "medium", "small", "square"])
}
image(includeAll: false) {
url(version: ["larger", "large", "medium", "small", "square"])
}
sale {
isAuction
isClosed
}
heightCm
widthCm
}
`

export const ContextMenuArtwork: React.FC<ContextMenuArtworkProps> = ({
children,
haptic = true,
Expand All @@ -83,7 +56,8 @@ export const ContextMenuArtwork: React.FC<ContextMenuArtworkProps> = ({

const { trackEvent } = useTracking()
const { showShareSheet } = useShareSheet()
const enableContextMenu = useFeatureFlag("AREnableArtworkCardContextMenuIOS")
const enableContextMenuIOS = useFeatureFlag("AREnableArtworkCardContextMenuIOS")
const enableContextMenuAndroid = useFeatureFlag("AREnableArtworkCardContextMenuAndroid")
const { submitMutation: dislikeArtworkMutation } = useDislikeArtwork()
const isIOS = Platform.OS === "ios"
const color = useColor()
Expand All @@ -92,7 +66,6 @@ export const ContextMenuArtwork: React.FC<ContextMenuArtworkProps> = ({

const { title, href, artists, slug, internalID, id, isHangable, image, sale } = artwork

const shouldDisplayContextMenu = isIOS && enableContextMenu
const enableCreateAlerts = !!artwork.artists?.length
const enableViewInRoom = LegacyNativeModules.ARCocoaConstantsModule.AREnabled && isHangable
const enableSupressArtwork = contextModule == "newWorksForYouRail"
Expand Down Expand Up @@ -245,16 +218,65 @@ export const ContextMenuArtwork: React.FC<ContextMenuArtworkProps> = ({
}),
}

if (!shouldDisplayContextMenu) {
return <>{children}</>
}

const artworkPreviewComponent = (artwork: ContextMenuArtworkPreviewCard_artwork$key) => {
return (
<ContextMenuArtworkPreviewCard artwork={artwork} artworkDisplayProps={artworkDisplayProps} />
)
}

const [androidVisible, setAndroidVisible] = useState(false)

const handleAndroidLongPress = () => {
setAndroidVisible(true)
}

// Fall back to a bottom sheet on Android
if (!isIOS && enableContextMenuAndroid) {
return (
<>
<TouchableHighlight
underlayColor={color("white100")}
activeOpacity={0.8}
onLongPress={handleAndroidLongPress}
onPress={undefined}
>
{children}
</TouchableHighlight>

<AutoHeightBottomSheet visible={androidVisible} onDismiss={() => setAndroidVisible(false)}>
<Flex pb={4} pt={1} mx={2} height="100%">
<Flex ml={-1} mb={1}>
{artworkPreviewComponent(artwork)}
</Flex>

<Join separator={<Separator borderColor="black10" my={1} />}>
{contextActions.map((action, index) => {
return (
<Touchable
key={index}
onPress={() => {
setAndroidVisible(false)

action.onPress?.()
}}
>
<Box>
<Text>{action.title}</Text>
</Box>
</Touchable>
)
})}
</Join>
</Flex>
</AutoHeightBottomSheet>
</>
)
}

if (!enableContextMenuIOS) {
return <>{children}</>
}

return (
<ContextMenu
actions={contextActions}
Expand All @@ -263,9 +285,38 @@ export const ContextMenuArtwork: React.FC<ContextMenuArtworkProps> = ({
preview={artworkPreviewComponent(artwork)}
hideShadows={true}
previewBackgroundColor={!!dark ? color("black100") : color("white100")}
disabled={!shouldDisplayContextMenu}
>
{children}
</ContextMenu>
)
}

const artworkFragment = graphql`
fragment ContextMenuArtwork_artwork on Artwork @argumentDefinitions(width: { type: "Int" }) {
...ContextMenuArtworkPreviewCard_artwork @arguments(width: $width)
...useSaveArtworkToArtworkLists_artwork

title
href
artistNames
artists(shallow: true) {
name
}
slug
internalID
id
isHangable
contextMenuImage: image {
url(version: ["larger", "large", "medium", "small", "square"])
}
image(includeAll: false) {
url(version: ["larger", "large", "medium", "small", "square"])
}
sale {
isAuction
isClosed
}
heightCm
widthCm
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { fireEvent, screen, waitFor } from "@testing-library/react-native"
import { ArtworkRailCardTestsQuery } from "__generated__/ArtworkRailCardTestsQuery.graphql"
import { ArtworkRailCard } from "app/Components/ArtworkRail/ArtworkRailCard"
import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"
import { Platform } from "react-native"
import { graphql } from "react-relay"

describe("ContextMenuArtwork", () => {
const { renderWithRelay } = setupTestWrapper<ArtworkRailCardTestsQuery>({
Component: (props) => {
if (!props.artwork) return null

return <ArtworkRailCard {...props} artwork={props.artwork} testID="artwork-card" />
},
query: graphql`
query ContextMenuArtworkTestsQuery @relay_test_operation {
artwork(id: "the-artwork") {
...ArtworkRailCard_artwork
}
}
`,
})

describe("on Android", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not able to test the context menu on iOS with react-native-context-menu-view.

beforeEach(() => {
Platform.OS = "android"

__globalStoreTestUtils__?.injectFeatureFlags({
AREnableArtworkCardContextMenuAndroid: true,
})
})

it("shows context menu on long press", async () => {
renderWithRelay()

const artworkCard = screen.getByTestId("artwork-card")

fireEvent(artworkCard, "onLongPress")

await waitFor(() => {
expect(screen.getByText("Create alert")).toBeOnTheScreen()
})
await waitFor(() => {
expect(screen.getByText("Share")).toBeOnTheScreen()
})
})

describe("when feature flag is disabled", () => {
beforeEach(() => {
__globalStoreTestUtils__?.injectFeatureFlags({
AREnableArtworkCardContextMenuAndroid: false,
})
})

it("does NOT show context menu on long press", async () => {
renderWithRelay()

const artworkCard = screen.getByTestId("artwork-card")

fireEvent(artworkCard, "onLongPress")

await waitFor(() => {
expect(screen.queryByText("Create alert")).not.toBeOnTheScreen()
})
})
})
})
})
6 changes: 6 additions & 0 deletions src/app/store/config/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ export const features = {
showInDevMenu: true,
echoFlagKey: "AREnableArtworkCardContextMenuIOS",
},
AREnableArtworkCardContextMenuAndroid: {
description: "Enable long press menu on artwork cards for Android",
readyForRelease: false,
showInDevMenu: true,
// echoFlagKey: "AREnableArtworkCardContextMenuAndroid",
},
} satisfies { [key: string]: FeatureDescriptor }

export interface DevToggleDescriptor {
Expand Down