From 1408cc219f454e4476b1fda6837030870b0730f9 Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sat, 18 Dec 2021 11:48:04 +0200 Subject: [PATCH 1/7] stories --- ios/Podfile.lock | 6 ++ package.json | 1 + .../elements/Touchable/Touchable.stories.tsx | 35 +++++++ src/palette/elements/Touchable/Touchable.tsx | 95 ++++++++++++++----- src/storybook/storybook.requires.js | 1 + yarn.lock | 5 + 6 files changed, 117 insertions(+), 26 deletions(-) create mode 100644 src/palette/elements/Touchable/Touchable.stories.tsx diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6a57ee5033e..2ea918ee3c0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -455,6 +455,8 @@ PODS: - react-native-config/App (= 1.4.11) - react-native-config/App (1.4.11): - React-Core + - react-native-context-menu-view (1.9.0): + - React - react-native-cookies (6.0.11): - React-Core - react-native-fbsdk-next (11.0.0): @@ -785,6 +787,7 @@ DEPENDENCIES: - react-native-appboy-sdk (from `../node_modules/react-native-appboy-sdk`) - "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)" - react-native-config (from `../node_modules/react-native-config`) + - react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`) - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" - react-native-fbsdk-next (from `../node_modules/react-native-fbsdk-next`) - react-native-flipper (from `../node_modules/react-native-flipper`) @@ -988,6 +991,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/cameraroll" react-native-config: :path: "../node_modules/react-native-config" + react-native-context-menu-view: + :path: "../node_modules/react-native-context-menu-view" react-native-cookies: :path: "../node_modules/@react-native-cookies/cookies" react-native-fbsdk-next: @@ -1203,6 +1208,7 @@ SPEC CHECKSUMS: react-native-appboy-sdk: e4e36787766637d5c60da264b66f4389ed479867 react-native-cameraroll: 463aff54e37cff27ea76eb792e6f1fa43b876320 react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b + react-native-context-menu-view: 1797ce85b52d810301268330704272b8cdb7a6e7 react-native-cookies: cd92f3824ed1e32a20802e8185101e14bb5b76da react-native-fbsdk-next: e5ea39f1b0f1e76deeb2538275559706f3b79e82 react-native-flipper: 8d0ada062fbc2c7460cc0d343aded6fc3f9c90dc diff --git a/package.json b/package.json index 861ee670171..0ed3345dc72 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "react-native-appboy-sdk": "1.35.1", "react-native-bootsplash": "3.2.0", "react-native-config": "https://github.com/artsy/react-native-config.git#v1.4.11-artsy", + "react-native-context-menu-view": "1.9.0", "react-native-credit-card-input": "0.4.1", "react-native-device-info": "10.3.0", "react-native-fast-image": "8.5.11", diff --git a/src/palette/elements/Touchable/Touchable.stories.tsx b/src/palette/elements/Touchable/Touchable.stories.tsx new file mode 100644 index 00000000000..57e0f414580 --- /dev/null +++ b/src/palette/elements/Touchable/Touchable.stories.tsx @@ -0,0 +1,35 @@ +import { storiesOf } from "@storybook/react-native" +import { Text } from "palette" +import { withHooks, withScreenDimensions, withTheme } from "storybook/decorators" +import { List } from "storybook/helpers" +import { Touchable } from "." +import { _test_DisplayState } from "../Button/Button" + +storiesOf("Touchable", module) + .addDecorator(withTheme) + .addDecorator(withScreenDimensions) + .addDecorator(withHooks) + .add("Presses", () => ( + + console.warn("regular")}> + Regular press + + console.warn("regular")} onLongPress={() => console.warn("long")}> + Regular and Long press + + console.warn("regular")} + onLongPress={[ + { + title: "Action 1", + subtitletitle: "Description 1", + systemIcon: "heart", + onPress: () => console.warn("1"), + }, + { title: "Action 2", subtitletitle: "Description 2", onPress: () => console.warn("2") }, + ]} + > + Regular and Context press + + + )) diff --git a/src/palette/elements/Touchable/Touchable.tsx b/src/palette/elements/Touchable/Touchable.tsx index 7f1e0c5b8e2..1ca3a12c0ef 100644 --- a/src/palette/elements/Touchable/Touchable.tsx +++ b/src/palette/elements/Touchable/Touchable.tsx @@ -1,22 +1,23 @@ import React from "react" -import { - GestureResponderEvent, - TouchableHighlight, - TouchableHighlightProps, - TouchableWithoutFeedback, -} from "react-native" +import { TouchableHighlight, TouchableHighlightProps, TouchableWithoutFeedback } from "react-native" +import ContextMenu, { ContextMenuAction, ContextMenuProps } from "react-native-context-menu-view" import Haptic, { HapticFeedbackTypes } from "react-native-haptic-feedback" import { useColor } from "../../hooks" import { Flex } from "../Flex" +interface ContextAction extends ContextMenuAction { + onPress?: () => void +} + interface ExtraTouchableProps { flex?: number haptic?: HapticFeedbackTypes | true noFeedback?: boolean + onLongPress?: ContextAction[] | TouchableHighlightProps["onLongPress"] } -export type TouchableProps = TouchableHighlightProps & ExtraTouchableProps +export type TouchableProps = Omit & ExtraTouchableProps /** * `haptic` can be used like: @@ -30,36 +31,78 @@ export const Touchable: React.FC = ({ haptic, noFeedback, onPress, + onLongPress, ...props }) => { const color = useColor() const inner = React.Children.count(children) === 1 ? children : {children} - const onPressWrapped = (evt: GestureResponderEvent) => { - if (onPress === undefined) { - return - } - - if (haptic !== undefined) { + const runHaptic = (pressFn: any | undefined) => { + if (pressFn !== undefined && haptic !== undefined) { Haptic.trigger(haptic === true ? "impactLight" : haptic) } + } - onPress(evt) + const onPressWrapped: TouchableHighlightProps["onPress"] = (e) => { + runHaptic(onPress) + onPress?.(e) } - return noFeedback ? ( - - {inner} - + const onLongPressFnWrapped: TouchableHighlightProps["onLongPress"] = !isActions(onLongPress) + ? (e) => { + runHaptic(onLongPress) + onLongPress?.(e) + } + : undefined + + const contextActions = isActions(onLongPress) + ? onLongPress.map((action) => { + const { onPress: ignored, ...rest } = action + return rest + }) + : undefined + + const contextOnPress: ContextMenuProps["onPress"] = isActions(onLongPress) + ? (e) => { + const onPressToCall = onLongPress[e.nativeEvent.index].onPress + + runHaptic(onPressToCall) + onPressToCall?.() + } + : undefined + + const InnerTouchable = () => + noFeedback ? ( + + {inner} + + ) : ( + + {inner} + + ) + + return contextActions !== undefined ? ( + + + + + ) : ( - - {inner} - + ) } + +const isActions = (longPressFn: TouchableProps["onLongPress"]): longPressFn is ContextAction[] => + Array.isArray(longPressFn) diff --git a/src/storybook/storybook.requires.js b/src/storybook/storybook.requires.js index 2ca55bd0917..cc2864efe6d 100644 --- a/src/storybook/storybook.requires.js +++ b/src/storybook/storybook.requires.js @@ -48,6 +48,7 @@ const getStories = () => { require("../palette/elements/SimpleMessage/SimpleMessage.stories.tsx"), require("../palette/elements/Tabs/Tabs.stories.tsx"), require("../palette/elements/Text/Text.stories.tsx"), + require("../palette/elements/Touchable/Touchable.stories.tsx"), require("../palette/elements/VisualClue/VisualClue.stories.tsx"), require("../palette/icons.stories.tsx"), require("../palette/organisms/screenStructure/Screen.stories.tsx"), diff --git a/yarn.lock b/yarn.lock index 100a0f4a957..416d5c3ee2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12438,6 +12438,11 @@ react-native-codegen@^0.0.7: version "1.4.11" resolved "https://github.com/artsy/react-native-config.git#609129ba486db9cb648e3bdd2043ab4a9f8692ea" +react-native-context-menu-view@1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/react-native-context-menu-view/-/react-native-context-menu-view-1.9.0.tgz#b8cf18134501d1ab9ca717859e5987382993f909" + integrity sha512-Gzrw8/e4LhoEAqeGc8eNx0jhkwUfSLGd/K3tlk8AJD2Atw53Nb5sYM3/PKG4vFHFs2xRuOAkv+FRL2pMiJk4Sw== + react-native-credit-card-input@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/react-native-credit-card-input/-/react-native-credit-card-input-0.4.1.tgz#e62dd1a9beeab6787e5373ba23b93831cbf5f3de" From 06afdb8f0da2ab4ae6f58126f47e76b99bb92bb9 Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 16:53:37 +0200 Subject: [PATCH 2/7] prettier --- package.json | 2 +- src/app/__fixtures__/ArtistFixture.js | 30 +++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 0ed3345dc72..34ede813508 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "open-url": "npx uri-scheme open", "postinstall": "yarn init-metaflags; prettier --write package.json; ./scripts/update-echo", "prepare": "patch-package && husky install", - "prettier-project": "yarn run prettier-write 'src/**/*.{ts,tsx}'", + "prettier-project": "yarn run prettier-write 'src/**/*.{ts,tsx,js,jsx}'", "prettier-write": "prettier --write", "relay": "[ -f data/complete.queryMap.json ] || echo '{}' > data/complete.queryMap.json && relay-compiler", "setup:artsy": "./scripts/setup-env-for-artsy", diff --git a/src/app/__fixtures__/ArtistFixture.js b/src/app/__fixtures__/ArtistFixture.js index 63b4cfcf143..2496fe1e12e 100644 --- a/src/app/__fixtures__/ArtistFixture.js +++ b/src/app/__fixtures__/ArtistFixture.js @@ -1,16 +1,16 @@ -"use strict"; -exports.__esModule = true; +"use strict" +exports.__esModule = true exports.ArtistFixture = { - gravityID: "pablo-picasso", - id: "QXJ0aXN0OnBhYmxvLXBpY2Fzc28=", - name: "Pablo Picasso", - is_followed: false, - initials: "P. P.", - href: "/artist/pablo-picasso", - nationality: "Spanish", - birthday: "1881", - deathday: "1973", - image: { - url: "https://d32dm0rphc51dk.cloudfront.net/i3rCA3IaKE-cLBnc-U5swQ/tall.jpg" - } -}; + gravityID: "pablo-picasso", + id: "QXJ0aXN0OnBhYmxvLXBpY2Fzc28=", + name: "Pablo Picasso", + is_followed: false, + initials: "P. P.", + href: "/artist/pablo-picasso", + nationality: "Spanish", + birthday: "1881", + deathday: "1973", + image: { + url: "https://d32dm0rphc51dk.cloudfront.net/i3rCA3IaKE-cLBnc-U5swQ/tall.jpg", + }, +} From bba3f27ce1f171245fb264f50fbbc0ccf8d8282c Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 17:03:57 +0200 Subject: [PATCH 3/7] no subtitles --- src/palette/elements/Touchable/Touchable.stories.tsx | 9 ++------- src/palette/elements/Touchable/Touchable.tsx | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/palette/elements/Touchable/Touchable.stories.tsx b/src/palette/elements/Touchable/Touchable.stories.tsx index 57e0f414580..b03de20b743 100644 --- a/src/palette/elements/Touchable/Touchable.stories.tsx +++ b/src/palette/elements/Touchable/Touchable.stories.tsx @@ -20,13 +20,8 @@ storiesOf("Touchable", module) console.warn("regular")} onLongPress={[ - { - title: "Action 1", - subtitletitle: "Description 1", - systemIcon: "heart", - onPress: () => console.warn("1"), - }, - { title: "Action 2", subtitletitle: "Description 2", onPress: () => console.warn("2") }, + { title: "Action 1", systemIcon: "heart", onPress: () => console.warn("1") }, + { title: "Action 2", onPress: () => console.warn("2") }, ]} > Regular and Context press diff --git a/src/palette/elements/Touchable/Touchable.tsx b/src/palette/elements/Touchable/Touchable.tsx index 1ca3a12c0ef..fddc22e58e5 100644 --- a/src/palette/elements/Touchable/Touchable.tsx +++ b/src/palette/elements/Touchable/Touchable.tsx @@ -6,7 +6,7 @@ import Haptic, { HapticFeedbackTypes } from "react-native-haptic-feedback" import { useColor } from "../../hooks" import { Flex } from "../Flex" -interface ContextAction extends ContextMenuAction { +interface ContextAction extends Omit { onPress?: () => void } @@ -59,7 +59,7 @@ export const Touchable: React.FC = ({ const contextActions = isActions(onLongPress) ? onLongPress.map((action) => { const { onPress: ignored, ...rest } = action - return rest + return { subtitletitle: "", ...rest } }) : undefined From cfbd768abe45b3a889ae8083315b370b6ea296ef Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 17:29:05 +0200 Subject: [PATCH 4/7] save action ready --- .../ArtworkGrids/ArtworkGridItem.tsx | 49 +++++++++++++++++-- src/palette/elements/Touchable/Touchable.tsx | 2 +- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx b/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx index 1566123c776..31699a963f8 100644 --- a/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx +++ b/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx @@ -1,10 +1,12 @@ -import { ScreenOwnerType, tappedMainArtworkGrid } from "@artsy/cohesion" +import { ContextModule, OwnerType, ScreenOwnerType, tappedMainArtworkGrid } from "@artsy/cohesion" import { ArtworkGridItem_artwork$data } from "__generated__/ArtworkGridItem_artwork.graphql" import { filterArtworksParams } from "app/Components/ArtworkFilter/ArtworkFilterHelpers" import { ArtworksFiltersStore } from "app/Components/ArtworkFilter/ArtworkFilterStore" import OpaqueImageView from "app/Components/OpaqueImageView/OpaqueImageView" +import { ArtworkGridItemSaveMutation } from "__generated__/ArtworkGridItemSaveMutation.graphql" import { navigate } from "app/navigation/navigate" +import { defaultEnvironment } from "app/relay/createEnvironment" import { GlobalStore, useFeatureFlag } from "app/store/GlobalStore" import { getUrgencyTag } from "app/utils/getUrgencyTag" import { @@ -13,6 +15,7 @@ import { RandomNumberGenerator, } from "app/utils/placeholders" import { refreshFavoriteArtworks } from "app/utils/refreshHelpers" +import { userHadMeaningfulInteraction } from "app/utils/userHadMeaningfulInteraction" import { useArtworkBidding } from "app/Websockets/auctions/useArtworkBidding" import { Box, @@ -27,7 +30,7 @@ import { } from "palette" import React, { useRef } from "react" import { View } from "react-native" -import { createFragmentContainer, graphql, useMutation } from "react-relay" +import { commitMutation, createFragmentContainer, graphql, useMutation } from "react-relay" import { useTracking } from "react-tracking" import { DurationProvider } from "../Countdown" import { LotCloseInfo } from "./LotCloseInfo" @@ -206,7 +209,47 @@ export const Artwork: React.FC = ({ !!artwork.sale?.extendedBiddingPeriodMinutes && !!artwork.sale?.extendedBiddingIntervalMinutes return ( - + { + commitMutation(defaultEnvironment, { + mutation: graphql` + mutation ArtworkGridItemSaveMutation($input: SaveArtworkInput!) { + saveArtwork(input: $input) { + artwork { + id + isSaved + } + } + } + `, + variables: { input: { artworkID: artwork.internalID, remove: artwork.isSaved } }, + // @ts-expect-error RELAY 12 MIGRATION + optimisticResponse: { + saveArtwork: { + artwork: { + id: artwork.id, + isSaved: !artwork.isSaved, + }, + }, + }, + onCompleted: () => + userHadMeaningfulInteraction({ + contextModule: ContextModule.artworkMetadata, + contextOwnerType: OwnerType.artwork, + contextOwnerId: artwork.internalID, + contextOwnerSlug: artwork.slug, + }), + }) + }, + }, + ]} + > {!!artwork.image && ( diff --git a/src/palette/elements/Touchable/Touchable.tsx b/src/palette/elements/Touchable/Touchable.tsx index fddc22e58e5..4b92bf35787 100644 --- a/src/palette/elements/Touchable/Touchable.tsx +++ b/src/palette/elements/Touchable/Touchable.tsx @@ -95,7 +95,7 @@ export const Touchable: React.FC = ({ return contextActions !== undefined ? ( - + From eed1e4724b059a34f7774fc57aa4a7274e3264c4 Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 17:51:14 +0200 Subject: [PATCH 5/7] better sharesheet. global --- package.json | 1 + src/app/AppProviders.tsx | 6 +- .../ArtworkGrids/ArtworkGridItem.tsx | 7 + src/app/Components/CustomShareSheet.tsx | 53 ---- .../CustomShareSheet/CustomShareSheet.tsx | 254 ++++++++++++++++++ src/app/Components/CustomShareSheet/atoms.ts | 26 ++ src/app/Components/ShareSheet/ShareSheet.tsx | 2 +- .../Artwork/Components/ArtworkHeader.tsx | 156 +---------- src/app/Scenes/Sale/Components/SaleHeader.tsx | 5 +- yarn.lock | 5 + 10 files changed, 317 insertions(+), 198 deletions(-) delete mode 100644 src/app/Components/CustomShareSheet.tsx create mode 100644 src/app/Components/CustomShareSheet/CustomShareSheet.tsx create mode 100644 src/app/Components/CustomShareSheet/atoms.ts diff --git a/package.json b/package.json index 34ede813508..4ac03a0c7af 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "google-libphonenumber": "3.2.30", "grapheme-splitter": "1.0.4", "html-entities": "2.3.2", + "jotai": "1.12.0", "jsc-android": "250230.2.1", "limiter": "^2.1.0", "lodash": "4.17.21", diff --git a/src/app/AppProviders.tsx b/src/app/AppProviders.tsx index e0f4122b0d0..37a757ef035 100644 --- a/src/app/AppProviders.tsx +++ b/src/app/AppProviders.tsx @@ -5,6 +5,7 @@ import { GestureHandlerRootView } from "react-native-gesture-handler" import { SafeAreaProvider } from "react-native-safe-area-context" import { RelayEnvironmentProvider } from "react-relay" import { ProvideScreenDimensions } from "shared/hooks" +import { CustomShareSheet } from "./Components/CustomShareSheet/CustomShareSheet" import { _FancyModalPageWrapper } from "./Components/FancyModal/FancyModalContext" import { PopoverMessageProvider } from "./Components/PopoverMessage/PopoverMessageProvider" import { RetryErrorBoundary } from "./Components/RetryErrorBoundary" @@ -37,7 +38,10 @@ export const AppProviders = ({ children }: { children?: React.ReactNode }) => ToastProvider, // uses: GlobalStoreProvider GravityWebsocketContextProvider, // uses GlobalStoreProvider ], - children + <> + {children} + + ) // Providers with preset props diff --git a/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx b/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx index 31699a963f8..733850b1461 100644 --- a/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx +++ b/src/app/Components/ArtworkGrids/ArtworkGridItem.tsx @@ -33,6 +33,7 @@ import { View } from "react-native" import { commitMutation, createFragmentContainer, graphql, useMutation } from "react-relay" import { useTracking } from "react-tracking" import { DurationProvider } from "../Countdown" +import { useCustomShareSheet } from "../CustomShareSheet/atoms" import { LotCloseInfo } from "./LotCloseInfo" import { LotProgressBar } from "./LotProgressBar" @@ -103,6 +104,7 @@ export const Artwork: React.FC = ({ const eableArtworkGridSaveIcon = useFeatureFlag("AREnableArtworkGridSaveIcon") const enableNewOpaqueImageView = useFeatureFlag("AREnableNewOpaqueImageComponent") const [saveArtwork] = useMutation(SaveArtworkMutation) + const sharesheet = useCustomShareSheet() let filterParams: any @@ -248,6 +250,11 @@ export const Artwork: React.FC = ({ }) }, }, + { + title: "Share", + systemIcon: "square.and.arrow.up", + onPress: () => sharesheet.show({ type: "artwork", slug: artwork.slug }), + }, ]} > diff --git a/src/app/Components/CustomShareSheet.tsx b/src/app/Components/CustomShareSheet.tsx deleted file mode 100644 index 5345ec6a45f..00000000000 --- a/src/app/Components/CustomShareSheet.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { ChevronIcon, Flex, Text, Touchable } from "palette" -import { useScreenDimensions } from "shared/hooks" -import { FancyModal } from "./FancyModal/FancyModal" -import { FancyModalHeader } from "./FancyModal/FancyModalHeader" - -interface CustomShareSheetProps { - visible: boolean - setVisible: (value: boolean) => void -} - -export const CustomShareSheet: React.FC = ({ - children, - visible, - setVisible, -}) => { - const { height: screenHeight } = useScreenDimensions() - - return ( - setVisible(false)} - > - setVisible(false)}> - Share - - {children} - - ) -} - -interface CustomShareSheetItemProps { - title: string - Icon: React.ReactNode - onPress?: () => void -} - -export const CustomShareSheetItem: React.FC = ({ - title, - Icon, - onPress, -}) => ( - - - {Icon} - - {title} - - - - - -) diff --git a/src/app/Components/CustomShareSheet/CustomShareSheet.tsx b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx new file mode 100644 index 00000000000..0b3c4d00595 --- /dev/null +++ b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx @@ -0,0 +1,254 @@ +import { + ActionType, + ContextModule, + CustomService, + OwnerType, + share, + Share as ShareType, +} from "@artsy/cohesion" +import Clipboard from "@react-native-clipboard/clipboard" +import { + CustomShareSheet_ArtworkQuery, + CustomShareSheet_ArtworkQuery$data, +} from "__generated__/CustomShareSheet_ArtworkQuery.graphql" +import { InstagramStoryViewShot } from "app/Scenes/Artwork/Components/InstagramStoryViewShot" +import { unsafe__getEnvironment } from "app/store/GlobalStore" +import { Schema } from "app/utils/track" +import { useCanOpenURL } from "app/utils/useCanOpenURL" +import { take } from "lodash" +import { + ChevronIcon, + Flex, + InstagramAppIcon, + LinkIcon, + MoreIcon, + ShareIcon, + Text, + Touchable, + WhatsAppAppIcon, +} from "palette" +import React, { useRef } from "react" +import { ScrollView } from "react-native" +import Share from "react-native-share" +import ViewShot from "react-native-view-shot" +import { useLazyLoadQuery } from "react-relay" +import { useTracking } from "react-tracking" +import { graphql } from "relay-runtime" +import RNFetchBlob from "rn-fetch-blob" +import { useScreenDimensions } from "shared/hooks" +import { FancyModal } from "../FancyModal/FancyModal" +import { FancyModalHeader } from "../FancyModal/FancyModalHeader" +import { useToast } from "../Toast/toastHook" +import { useCustomShareSheetInternal } from "./atoms" + +export const CustomShareSheet = () => { + const { visible, item, hide } = useCustomShareSheetInternal() + const showInstagramStoriesItem = useCanOpenURL("instagram://user?username=instagram") + const showWhatsAppItem = useCanOpenURL("whatsapp://send?phone=+491898") + const { height: screenHeight } = useScreenDimensions() + const toast = useToast() + const shotRef = useRef(null) + const { trackEvent } = useTracking() + const data = useLazyLoadQuery(artworkQuery, { + slug: item?.slug ?? "", + skip: !item, + }) + + if (!data || !data.artwork) { + return null + } + + const currentImage = (data.artwork.images ?? [])[item?.currentImageIndex ?? 0] + const currentImageUrl = (currentImage?.url ?? "").replace(":version", "normalized") + const smallImageURL = (currentImage?.url ?? "").replace(":version", "small") + + const shareArtworkOnInstagramStory = async () => { + const base64RawData = await shotRef.current!.capture!() + const base64Data = `data:image/png;base64,${base64RawData}` + + await Share.shareSingle({ + social: Share.Social.INSTAGRAM_STORIES, + backgroundImage: base64Data, + }) + trackEvent( + share( + tracks.customShare( + CustomService.instagram_stories, + data.artwork!.internalID, + data.artwork?.slug + ) + ) + ) + hide() + } + + const shareArtworkOnWhatsApp = async () => { + const details = shareContent(data.artwork!) + + await Share.shareSingle({ + social: Share.Social.WHATSAPP, + message: details.message ?? "", + url: details.url, + }) + trackEvent( + share( + tracks.customShare(CustomService.whatsapp, data.artwork!.internalID, data.artwork?.slug) + ) + ) + hide() + } + + const shareArtworkCopyLink = () => { + Clipboard.setString(`${unsafe__getEnvironment().webURL}${data.artwork?.href}`) + trackEvent( + share( + tracks.customShare(CustomService.copy_link, data.artwork!.internalID, data.artwork?.slug) + ) + ) + hide() + toast.show("Copied to Clipboard", "middle", { Icon: ShareIcon }) + } + + const shareArtwork = async () => { + trackEvent({ + action_name: Schema.ActionNames.Share, + action_type: Schema.ActionTypes.Tap, + context_module: Schema.ContextModules.ArtworkActions, + }) + + const details = shareContent(data.artwork!) + + const resp = await RNFetchBlob.config({ + fileCache: true, + }).fetch("GET", smallImageURL) + + const base64RawData = await resp.base64() + const base64Data = `data:image/png;base64,${base64RawData}` + + try { + const res = await Share.open({ + title: details.title ?? "", + url: base64Data, + message: details.message + "\n" + details.url, + }) + trackEvent(share(tracks.iosShare(res.message, data.artwork!.internalID, data.artwork?.slug))) + } catch (err) { + console.log({ err }) + } finally { + hide() + } + } + + return ( + hide()}> + hide()}> + Share + + + + + {showWhatsAppItem ? ( + } + onPress={shareArtworkOnWhatsApp} + /> + ) : null} + {showInstagramStoriesItem ? ( + } + onPress={shareArtworkOnInstagramStory} + /> + ) : null} + + } + onPress={shareArtworkCopyLink} + /> + } onPress={shareArtwork} /> + + + ) +} + +interface CustomShareSheetItemProps { + title: string + Icon: React.ReactNode + onPress?: () => void +} + +const CustomShareSheetItem: React.FC = ({ title, Icon, onPress }) => ( + + + {Icon} + + {title} + + + + + +) + +export const shareContent = ( + artwork: NonNullable +) => { + const { title, href, artists } = artwork + let computedTitle = "" + + if (artists && artists.length) { + const names = take(artists, 3).map((artist) => artist?.name ?? "") + computedTitle = `${title} by ${names.join(", ")} on Artsy` + } else if (title) { + computedTitle = `${title} on Artsy` + } + + return { + title: computedTitle, + message: computedTitle, + url: `${unsafe__getEnvironment().webURL}${href}?utm_content=artwork-share`, + } +} + +const artworkQuery = graphql` + query CustomShareSheet_ArtworkQuery($slug: String!, $skip: Boolean!) { + artwork(id: $slug) @skip(if: $skip) { + slug + internalID + href + images { + url: imageURL + } + title + artists { + name + } + } + } +` + +export const tracks = { + customShare: (service: string, id: string, slug?: string): ShareType => ({ + action: ActionType.share, + context_module: ContextModule.artworkImage, + context_owner_type: OwnerType.artwork, + context_owner_id: id, + context_owner_slug: slug, + service, + }), + iosShare: (app: string, id: string, slug?: string): ShareType => ({ + action: ActionType.share, + context_module: ContextModule.artworkImage, + context_owner_type: OwnerType.artwork, + context_owner_id: id, + context_owner_slug: slug, + service: app, + }), +} diff --git a/src/app/Components/CustomShareSheet/atoms.ts b/src/app/Components/CustomShareSheet/atoms.ts new file mode 100644 index 00000000000..0ba9c4e3a75 --- /dev/null +++ b/src/app/Components/CustomShareSheet/atoms.ts @@ -0,0 +1,26 @@ +import { atom, useAtom } from "jotai" + +const visibleAtom = atom(false) +const itemAtom = atom<{ type: "artwork"; slug: string; currentImageIndex?: number } | null>(null) + +export const useCustomShareSheet = () => { + const [, setVisible] = useAtom(visibleAtom) + const [item, setItem] = useAtom(itemAtom) + + const show = (i: typeof item) => { + setItem(i) + setVisible(true) + } + + const hide = () => void setVisible(false) + + return { show, hide } +} + +export const useCustomShareSheetInternal = () => { + const { show, hide } = useCustomShareSheet() + const [visible] = useAtom(visibleAtom) + const [item] = useAtom(itemAtom) + + return { visible, item, show, hide } +} diff --git a/src/app/Components/ShareSheet/ShareSheet.tsx b/src/app/Components/ShareSheet/ShareSheet.tsx index e2e1252e5e8..f2bcbe587f4 100644 --- a/src/app/Components/ShareSheet/ShareSheet.tsx +++ b/src/app/Components/ShareSheet/ShareSheet.tsx @@ -9,7 +9,7 @@ import { ScrollView } from "react-native" import RNShare, { ShareOptions } from "react-native-share" import ViewShot from "react-native-view-shot" import { useTracking } from "react-tracking" -import { CustomShareSheet, CustomShareSheetItem } from "../CustomShareSheet" +import { CustomShareSheet, CustomShareSheetItem } from "../CustomShareSheet/CustomShareSheet" import { useToast } from "../Toast/toastHook" import { getBase64Data, getShareMessage, getShareURL } from "./helpers" diff --git a/src/app/Scenes/Artwork/Components/ArtworkHeader.tsx b/src/app/Scenes/Artwork/Components/ArtworkHeader.tsx index 9ff688354aa..9f889488069 100644 --- a/src/app/Scenes/Artwork/Components/ArtworkHeader.tsx +++ b/src/app/Scenes/Artwork/Components/ArtworkHeader.tsx @@ -1,29 +1,12 @@ -import { ContextModule, CustomService, OwnerType, share } from "@artsy/cohesion" -import Clipboard from "@react-native-clipboard/clipboard" import { ArtworkHeader_artwork$data } from "__generated__/ArtworkHeader_artwork.graphql" -import { CustomShareSheet, CustomShareSheetItem } from "app/Components/CustomShareSheet" -import { useToast } from "app/Components/Toast/toastHook" -import { unsafe__getEnvironment, useDevToggle } from "app/store/GlobalStore" -import { Schema } from "app/utils/track" -import { useCanOpenURL } from "app/utils/useCanOpenURL" -import { - Box, - Flex, - InstagramAppIcon, - LinkIcon, - MoreIcon, - ShareIcon, - Spacer, - WhatsAppAppIcon, -} from "palette" -import React, { useRef, useState } from "react" -import { Button, Modal, ScrollView } from "react-native" -import Share from "react-native-share" -import ViewShot from "react-native-view-shot" +import { useCustomShareSheet } from "app/Components/CustomShareSheet/atoms" +import { useDevToggle } from "app/store/GlobalStore" +import { Box, Flex, Spacer } from "palette" +import React, { useState } from "react" +import { Button, Modal } from "react-native" import { createFragmentContainer, graphql } from "react-relay" -import { useTracking } from "react-tracking" import { useScreenDimensions } from "shared/hooks" -import { ArtworkActionsFragmentContainer as ArtworkActions, shareContent } from "./ArtworkActions" +import { ArtworkActionsFragmentContainer as ArtworkActions } from "./ArtworkActions" import { ArtworkTombstoneFragmentContainer as ArtworkTombstone } from "./ArtworkTombstone" import { ImageCarouselFragmentContainer } from "./ImageCarousel/ImageCarousel" import { InstagramStoryViewShot } from "./InstagramStoryViewShot" @@ -44,80 +27,13 @@ export const ArtworkHeader: React.FC = (props) => { const { artwork, refetchArtwork } = props const screenDimensions = useScreenDimensions() const [currentImageIndex, setCurrentImageIndex] = useState(0) - const { trackEvent } = useTracking() const debugInstagramShot = useDevToggle("DTShowInstagramShot") const [showInstagramShot, setShowInstagramShot] = useState(false) - const shotRef = useRef(null) - const [shareSheetVisible, setShareSheetVisible] = useState(false) - const toast = useToast() - - const showWhatsAppItem = useCanOpenURL("whatsapp://send?phone=+491898") - const showInstagramStoriesItem = useCanOpenURL("instagram://user?username=instagram") + const sharesheet = useCustomShareSheet() const currentImage = (artwork.images ?? [])[currentImageIndex] const currentImageUrl = (currentImage?.url ?? "").replace(":version", "large") - const shareArtwork = async () => { - trackEvent({ - action_name: Schema.ActionNames.Share, - action_type: Schema.ActionTypes.Tap, - context_module: Schema.ContextModules.ArtworkActions, - }) - - const { title, href, artists } = artwork - const details = shareContent(title!, href!, artists) - - const base64RawData = await shotRef.current!.capture!() - const base64Data = `data:image/png;base64,${base64RawData}` - - try { - const res = await Share.open({ - title: details.title ?? "", - url: base64Data, - message: details.message + "\n" + details.url, - }) - trackEvent(share(tracks.iosShare(res.message, artwork.internalID, artwork.slug))) - } catch (err) { - console.log({ err }) - } finally { - setShareSheetVisible(false) - } - } - - const shareArtworkOnWhatsApp = async () => { - const { title, href, artists } = artwork - const details = shareContent(title!, href!, artists) - - await Share.shareSingle({ - social: Share.Social.WHATSAPP, - message: details.message ?? "", - url: details.url, - }) - trackEvent(share(tracks.customShare(CustomService.whatsapp, artwork.internalID, artwork.slug))) - setShareSheetVisible(false) - } - - const shareArtworkOnInstagramStory = async () => { - const base64RawData = await shotRef.current!.capture!() - const base64Data = `data:image/png;base64,${base64RawData}` - - await Share.shareSingle({ - social: Share.Social.INSTAGRAM_STORIES, - backgroundImage: base64Data, - }) - trackEvent( - share(tracks.customShare(CustomService.instagram_stories, artwork.internalID, artwork.slug)) - ) - setShareSheetVisible(false) - } - - const shareArtworkCopyLink = async () => { - Clipboard.setString(`${unsafe__getEnvironment().webURL}${artwork.href!}`) - trackEvent(share(tracks.customShare(CustomService.copy_link, artwork.internalID, artwork.slug))) - setShareSheetVisible(false) - toast.show("Copied to Clipboard", "middle", { Icon: ShareIcon }) - } - return ( <> @@ -140,9 +56,13 @@ export const ArtworkHeader: React.FC = (props) => { { - setShareSheetVisible(true) - }} + shareOnPress={() => + void sharesheet.show({ + type: "artwork", + slug: artwork.slug, + currentImageIndex, + }) + } /> @@ -150,37 +70,6 @@ export const ArtworkHeader: React.FC = (props) => { - - - - - {showWhatsAppItem ? ( - } - onPress={() => shareArtworkOnWhatsApp()} - /> - ) : null} - {showInstagramStoriesItem ? ( - } - onPress={() => shareArtworkOnInstagramStory()} - /> - ) : null} - } - onPress={() => shareArtworkCopyLink()} - /> - } onPress={() => shareArtwork()} /> - - {debugInstagramShot && showInstagramShot ? ( setShowInstagramShot(false)}> @@ -224,20 +113,3 @@ export const ArtworkHeaderFragmentContainer = createFragmentContainer(ArtworkHea } `, }) - -export const tracks = { - customShare: (service: string, id: string, slug?: string) => ({ - context_module: ContextModule.artworkImage, - context_owner_type: OwnerType.artwork, - context_owner_id: id, - context_owner_slug: slug, - service, - }), - iosShare: (app: string, id: string, slug?: string) => ({ - context_module: ContextModule.artworkImage, - context_owner_type: OwnerType.artwork, - context_owner_id: id, - context_owner_slug: slug, - service: app, - }), -} diff --git a/src/app/Scenes/Sale/Components/SaleHeader.tsx b/src/app/Scenes/Sale/Components/SaleHeader.tsx index 9038f6a91a6..0837673854f 100644 --- a/src/app/Scenes/Sale/Components/SaleHeader.tsx +++ b/src/app/Scenes/Sale/Components/SaleHeader.tsx @@ -1,6 +1,9 @@ import Clipboard from "@react-native-clipboard/clipboard" import { SaleHeader_sale$data } from "__generated__/SaleHeader_sale.graphql" -import { CustomShareSheet, CustomShareSheetItem } from "app/Components/CustomShareSheet" +import { + CustomShareSheet, + CustomShareSheetItem, +} from "app/Components/CustomShareSheet/CustomShareSheet" import { getShareURL } from "app/Components/ShareSheet/helpers" import { useToast } from "app/Components/Toast/toastHook" import { getAbsoluteTimeOfSale, saleTime, useRelativeTimeOfSale } from "app/utils/saleTime" diff --git a/yarn.lock b/yarn.lock index 416d5c3ee2f..06e2921449e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9830,6 +9830,11 @@ joi@^17.2.1: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +jotai@1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.12.0.tgz#2e22760289458f636f689ae0aa71cc4a089aa312" + integrity sha512-IhyBmjxU1sE2Ni/MUK7gQAb8QvCM6yd1/K5jtQzgQBmmjCjgfXZkkk1rYlQAIRp2KoQk0Y+yzhm1f5cZ7kegnw== + jpeg-js@0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" From 99d8dfbadde3054945486046a18ccfccd4dbb505 Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 17:58:18 +0200 Subject: [PATCH 6/7] more --- .../CustomShareSheet/CustomShareSheet.tsx | 2 +- src/app/Components/CustomShareSheet/atoms.ts | 6 ++- src/app/Scenes/Sale/Components/SaleHeader.tsx | 51 ++----------------- 3 files changed, 10 insertions(+), 49 deletions(-) diff --git a/src/app/Components/CustomShareSheet/CustomShareSheet.tsx b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx index 0b3c4d00595..1a7e14b6948 100644 --- a/src/app/Components/CustomShareSheet/CustomShareSheet.tsx +++ b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx @@ -184,7 +184,7 @@ interface CustomShareSheetItemProps { onPress?: () => void } -const CustomShareSheetItem: React.FC = ({ title, Icon, onPress }) => ( +const CustomShareSheetItem = ({ title, Icon, onPress }: CustomShareSheetItemProps) => ( {Icon} diff --git a/src/app/Components/CustomShareSheet/atoms.ts b/src/app/Components/CustomShareSheet/atoms.ts index 0ba9c4e3a75..259230e3514 100644 --- a/src/app/Components/CustomShareSheet/atoms.ts +++ b/src/app/Components/CustomShareSheet/atoms.ts @@ -1,7 +1,11 @@ import { atom, useAtom } from "jotai" +type Item = + | { type: "artwork"; slug: string; currentImageIndex?: number } + | { type: "sale"; slug: string } + const visibleAtom = atom(false) -const itemAtom = atom<{ type: "artwork"; slug: string; currentImageIndex?: number } | null>(null) +const itemAtom = atom(null) export const useCustomShareSheet = () => { const [, setVisible] = useAtom(visibleAtom) diff --git a/src/app/Scenes/Sale/Components/SaleHeader.tsx b/src/app/Scenes/Sale/Components/SaleHeader.tsx index 0837673854f..89350ce125c 100644 --- a/src/app/Scenes/Sale/Components/SaleHeader.tsx +++ b/src/app/Scenes/Sale/Components/SaleHeader.tsx @@ -1,17 +1,9 @@ -import Clipboard from "@react-native-clipboard/clipboard" import { SaleHeader_sale$data } from "__generated__/SaleHeader_sale.graphql" -import { - CustomShareSheet, - CustomShareSheetItem, -} from "app/Components/CustomShareSheet/CustomShareSheet" -import { getShareURL } from "app/Components/ShareSheet/helpers" -import { useToast } from "app/Components/Toast/toastHook" +import { useCustomShareSheet } from "app/Components/CustomShareSheet/atoms" import { getAbsoluteTimeOfSale, saleTime, useRelativeTimeOfSale } from "app/utils/saleTime" import moment from "moment" -import { Flex, LinkIcon, MoreIcon, ShareIcon, Text, Touchable } from "palette" -import React, { useState } from "react" +import { Flex, ShareIcon, Text, Touchable } from "palette" import { Animated, Dimensions, View } from "react-native" -import RNShare, { ShareOptions } from "react-native-share" import { createFragmentContainer, graphql } from "react-relay" import { useScreenDimensions } from "shared/hooks" import { CaretButton } from "../../../Components/Buttons/CaretButton" @@ -28,12 +20,9 @@ interface Props { } export const SaleHeader: React.FC = ({ sale, scrollAnim }) => { - const [shareSheetVisible, setShareSheetVisible] = useState(false) - + const sharesheet = useCustomShareSheet() const enableAuctionShare = useFeatureFlag("AREnableAuctionShareButton") - const toast = useToast() - const saleTimeDetails = saleTime(sale) const absoluteTimeOfSale = getAbsoluteTimeOfSale(sale) @@ -42,31 +31,6 @@ export const SaleHeader: React.FC = ({ sale, scrollAnim }) => { const cascadingEndTimeFeatureEnabled = sale.cascadingEndTimeIntervalMinutes - const handleCopyLinkPress = () => { - const clipboardLink = getShareURL(sale.href!) - - setShareSheetVisible(false) - Clipboard.setString(clipboardLink) - toast.show("Copied to Clipboard", "middle", { Icon: ShareIcon }) - } - - const handleMorePress = async () => { - try { - const url = getShareURL(sale.href!) - const message = sale.name + " on Artsy" - const shareOptions: ShareOptions = { - title: message, - message: message + "\n" + url, - } - - await RNShare.open(shareOptions) - } catch (error) { - console.log(error) - } finally { - setShareSheetVisible(false) - } - } - return ( <> {!!sale.coverImage?.url && ( @@ -138,9 +102,7 @@ export const SaleHeader: React.FC = ({ sale, scrollAnim }) => { {!!enableAuctionShare && ( { - setShareSheetVisible(true) - }} + onPress={() => void sharesheet.show({ type: "sale", slug: sale.slug })} style={{ width: 30, justifyContent: "flex-end", @@ -193,11 +155,6 @@ export const SaleHeader: React.FC = ({ sale, scrollAnim }) => { /> - - - } onPress={handleMorePress} /> - } onPress={handleCopyLinkPress} /> - ) } From 510f1347663a4face4c1936162997591e3fad277 Mon Sep 17 00:00:00 2001 From: Pavlos Vinieratos Date: Sun, 25 Dec 2022 18:09:11 +0200 Subject: [PATCH 7/7] wip --- .../Artist/ArtistHeaderFloatingButtons.tsx | 36 ++-- .../CustomShareSheet.tests.tsx} | 3 +- .../CustomShareSheet/CustomShareSheet.tsx | 15 ++ src/app/Components/CustomShareSheet/atoms.ts | 5 +- .../helpers.tests.ts | 0 .../helpers.ts | 0 src/app/Components/CustomShareSheet/types.ts | 4 + src/app/Components/ShareSheet/ShareSheet.tsx | 181 ------------------ src/app/Scenes/ViewingRoom/ViewingRoom.tsx | 2 +- 9 files changed, 34 insertions(+), 212 deletions(-) rename src/app/Components/{ShareSheet/ShareSheet.tests.tsx => CustomShareSheet/CustomShareSheet.tests.tsx} (99%) rename src/app/Components/{ShareSheet => CustomShareSheet}/helpers.tests.ts (100%) rename src/app/Components/{ShareSheet => CustomShareSheet}/helpers.ts (100%) create mode 100644 src/app/Components/CustomShareSheet/types.ts delete mode 100644 src/app/Components/ShareSheet/ShareSheet.tsx diff --git a/src/app/Components/Artist/ArtistHeaderFloatingButtons.tsx b/src/app/Components/Artist/ArtistHeaderFloatingButtons.tsx index fa21daf1a1d..37e2355eb45 100644 --- a/src/app/Components/Artist/ArtistHeaderFloatingButtons.tsx +++ b/src/app/Components/Artist/ArtistHeaderFloatingButtons.tsx @@ -1,13 +1,12 @@ -import { ContextModule, OwnerType } from "@artsy/cohesion" import { ArtistHeaderFloatingButtons_artist$data } from "__generated__/ArtistHeaderFloatingButtons_artist.graphql" import { HeaderButton } from "app/Components/HeaderButton" -import { ShareSheet } from "app/Components/ShareSheet/ShareSheet" import { useStickyTabPageContext } from "app/Components/StickyTabPage/StickyTabPageContext" import { goBack } from "app/navigation/navigate" import { ChevronIcon, ShareIcon } from "palette" -import React, { Fragment, useRef, useState } from "react" +import React, { useRef, useState } from "react" import Animated, { block, call, cond, onChange, set, useCode } from "react-native-reanimated" import { createFragmentContainer, graphql } from "react-relay" +import { useCustomShareSheet } from "../CustomShareSheet/atoms" interface ArtistHeaderFloatingButtonsProps { artist: ArtistHeaderFloatingButtons_artist$data @@ -20,7 +19,7 @@ const SHARE_ICON_SIZE = 23 export const ArtistHeaderFloatingButtons: React.FC = ({ artist, }) => { - const [shareSheetVisible, setShareSheetVisible] = useState(false) + const sharesheet = useCustomShareSheet() const [hideButton, setHideButton] = useState(false) const { headerOffsetY } = useStickyTabPageContext() @@ -41,12 +40,8 @@ export const ArtistHeaderFloatingButtons: React.FC { - setShareSheetVisible(true) - } - return ( - + <> + sharesheet.show({ + type: "artist", + slug: artist.slug, + }) + } position="right" applySafeAreaTopInsets={false} > - - - + ) } diff --git a/src/app/Components/ShareSheet/ShareSheet.tests.tsx b/src/app/Components/CustomShareSheet/CustomShareSheet.tests.tsx similarity index 99% rename from src/app/Components/ShareSheet/ShareSheet.tests.tsx rename to src/app/Components/CustomShareSheet/CustomShareSheet.tests.tsx index 9245193b5b3..131d7791752 100644 --- a/src/app/Components/ShareSheet/ShareSheet.tests.tsx +++ b/src/app/Components/CustomShareSheet/CustomShareSheet.tests.tsx @@ -6,7 +6,8 @@ import { renderWithWrappers } from "app/tests/renderWithWrappers" import { useCanOpenURL } from "app/utils/useCanOpenURL" import Share from "react-native-share" import * as helpers from "./helpers" -import { ShareSheet, ShareSheetProps } from "./ShareSheet" + +// TODO: these tests need some rewriting const setVisibleMock = jest.fn() diff --git a/src/app/Components/CustomShareSheet/CustomShareSheet.tsx b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx index 1a7e14b6948..97ee606ac91 100644 --- a/src/app/Components/CustomShareSheet/CustomShareSheet.tsx +++ b/src/app/Components/CustomShareSheet/CustomShareSheet.tsx @@ -49,6 +49,7 @@ export const CustomShareSheet = () => { const toast = useToast() const shotRef = useRef(null) const { trackEvent } = useTracking() + // TODO: we need to handle thr artist and the sale case const data = useLazyLoadQuery(artworkQuery, { slug: item?.slug ?? "", skip: !item, @@ -234,6 +235,20 @@ const artworkQuery = graphql` } ` +const artistQuery = graphql` + query CustomShareSheet_ArtistQuery($slug: String!, $skip: Boolean!) { + artist(id: $slug) @skip(if: $skip) { + internalID + slug + href + name + image { + url(version: "large") + } + } + } +` +// TODO: needs the other stuff, like sale and artist export const tracks = { customShare: (service: string, id: string, slug?: string): ShareType => ({ action: ActionType.share, diff --git a/src/app/Components/CustomShareSheet/atoms.ts b/src/app/Components/CustomShareSheet/atoms.ts index 259230e3514..9c62115002f 100644 --- a/src/app/Components/CustomShareSheet/atoms.ts +++ b/src/app/Components/CustomShareSheet/atoms.ts @@ -1,8 +1,5 @@ import { atom, useAtom } from "jotai" - -type Item = - | { type: "artwork"; slug: string; currentImageIndex?: number } - | { type: "sale"; slug: string } +import { Item } from "./types" const visibleAtom = atom(false) const itemAtom = atom(null) diff --git a/src/app/Components/ShareSheet/helpers.tests.ts b/src/app/Components/CustomShareSheet/helpers.tests.ts similarity index 100% rename from src/app/Components/ShareSheet/helpers.tests.ts rename to src/app/Components/CustomShareSheet/helpers.tests.ts diff --git a/src/app/Components/ShareSheet/helpers.ts b/src/app/Components/CustomShareSheet/helpers.ts similarity index 100% rename from src/app/Components/ShareSheet/helpers.ts rename to src/app/Components/CustomShareSheet/helpers.ts diff --git a/src/app/Components/CustomShareSheet/types.ts b/src/app/Components/CustomShareSheet/types.ts new file mode 100644 index 00000000000..f0a8788a2ab --- /dev/null +++ b/src/app/Components/CustomShareSheet/types.ts @@ -0,0 +1,4 @@ +export type Item = + | { type: "artwork"; slug: string; currentImageIndex?: number } + | { type: "sale"; slug: string } + | { type: "artist"; slug: string } diff --git a/src/app/Components/ShareSheet/ShareSheet.tsx b/src/app/Components/ShareSheet/ShareSheet.tsx deleted file mode 100644 index f2bcbe587f4..00000000000 --- a/src/app/Components/ShareSheet/ShareSheet.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { ActionType, ContextModule, CustomService, OwnerType, Share, share } from "@artsy/cohesion" -import Clipboard from "@react-native-clipboard/clipboard" -import { InstagramStoryViewShot } from "app/Scenes/Artwork/Components/InstagramStoryViewShot" -import { Schema } from "app/utils/track" -import { useCanOpenURL } from "app/utils/useCanOpenURL" -import { InstagramAppIcon, LinkIcon, MoreIcon, ShareIcon, WhatsAppAppIcon } from "palette" -import React, { useRef } from "react" -import { ScrollView } from "react-native" -import RNShare, { ShareOptions } from "react-native-share" -import ViewShot from "react-native-view-shot" -import { useTracking } from "react-tracking" -import { CustomShareSheet, CustomShareSheetItem } from "../CustomShareSheet/CustomShareSheet" -import { useToast } from "../Toast/toastHook" -import { getBase64Data, getShareMessage, getShareURL } from "./helpers" - -interface ShareEntry { - internalID: string - slug: string - href: string - artistNames: string[] - title?: string - imageURL?: string -} - -export interface ShareSheetProps { - visible: boolean - entry: ShareEntry - showWhatsapp?: boolean - showInstagram?: boolean - contextModule: ContextModule - componentContextModule?: ContextModule - ownerType: OwnerType - setVisible: (isVisible: boolean) => void -} - -export const ShareSheet: React.FC = (props) => { - const { - visible, - entry, - contextModule, - componentContextModule, - ownerType, - showWhatsapp = true, - showInstagram = true, - setVisible, - } = props - const toast = useToast() - const { trackEvent } = useTracking() - const shotRef = useRef(null) - const canOpenWhatsapp = useCanOpenURL("whatsapp://send?phone=+491898") - const canOpenInstagram = useCanOpenURL("instagram://user?username=instagram") - - const handleShareOnWhatsAppPress = async () => { - try { - const url = getShareURL(entry.href) - const message = getShareMessage(entry.artistNames, entry.title) - const event = tracks.share(contextModule, ownerType, entry, CustomService.whatsapp) - - await RNShare.shareSingle({ - social: RNShare.Social.WHATSAPP, - message, - url, - }) - - trackEvent(share(event)) - } catch (error) { - console.log(error) - } finally { - setVisible(false) - } - } - - const handleShareOnInstagramStoryPress = async () => { - try { - const base64Data = await getBase64Data(shotRef.current!) - const event = tracks.share(contextModule, ownerType, entry, CustomService.instagram_stories) - - await RNShare.shareSingle({ - social: RNShare.Social.INSTAGRAM_STORIES, - backgroundImage: base64Data, - }) - trackEvent(share(event)) - } catch (error) { - console.log(error) - } finally { - setVisible(false) - } - } - - const handleMorePress = async () => { - trackEvent({ - action_name: Schema.ActionNames.Share, - action_type: Schema.ActionTypes.Tap, - context_module: componentContextModule ?? contextModule, - }) - - try { - const url = getShareURL(entry.href) - const message = getShareMessage(entry.artistNames, entry.title) - const shareOptions: ShareOptions = { - title: message, - message: message + "\n" + url, - } - - if (entry.imageURL && shotRef.current) { - const base64Data = await getBase64Data(shotRef.current) - - shareOptions.url = base64Data - } - - const res = await RNShare.open(shareOptions) - const event = tracks.share(contextModule, ownerType, entry, res.message) - - trackEvent(share(event)) - } catch (error) { - console.log(error) - } finally { - setVisible(false) - } - } - - const handleCopyLinkPress = () => { - const clipboardLink = getShareURL(entry.href) - const event = tracks.share(contextModule, ownerType, entry, CustomService.copy_link) - - setVisible(false) - Clipboard.setString(clipboardLink) - toast.show("Copied to Clipboard", "middle", { Icon: ShareIcon }) - trackEvent(share(event)) - } - - return ( - - - {entry.imageURL ? ( - - ) : null} - - {showWhatsapp && canOpenWhatsapp ? ( - } - onPress={handleShareOnWhatsAppPress} - /> - ) : null} - - {entry.imageURL && showInstagram && canOpenInstagram ? ( - } - onPress={handleShareOnInstagramStoryPress} - /> - ) : null} - - } onPress={handleCopyLinkPress} /> - } onPress={handleMorePress} /> - - - ) -} - -export const tracks = { - share: ( - contextModule: ContextModule, - ownerType: OwnerType, - entry: ShareEntry, - service: string - ): Share => ({ - action: ActionType.share, - context_module: contextModule, - context_owner_type: ownerType, - context_owner_id: entry.internalID, - context_owner_slug: entry.slug, - service, - }), -} diff --git a/src/app/Scenes/ViewingRoom/ViewingRoom.tsx b/src/app/Scenes/ViewingRoom/ViewingRoom.tsx index c54353c8ce8..52cf549c84f 100644 --- a/src/app/Scenes/ViewingRoom/ViewingRoom.tsx +++ b/src/app/Scenes/ViewingRoom/ViewingRoom.tsx @@ -1,6 +1,6 @@ import { ViewingRoom_viewingRoom$data } from "__generated__/ViewingRoom_viewingRoom.graphql" import { ViewingRoomQuery } from "__generated__/ViewingRoomQuery.graphql" -import { getShareURL } from "app/Components/ShareSheet/helpers" +import { getShareURL } from "app/Components/CustomShareSheet/helpers" import { navigate } from "app/navigation/navigate" import { defaultEnvironment } from "app/relay/createEnvironment" import renderWithLoadProgress from "app/utils/renderWithLoadProgress"